blob: dd3bfc3c4a9370157d2b699c3b6d54087c9fa7c6 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.os.Environment;
import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Handles reading and writing of the owner transfer metadata file.
*
* Before we perform a device or profile owner transfer, we save this xml file with information
* about the current admin, target admin, user id and admin type (device owner or profile owner).
* After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after
* device boot the file is still there, this indicates that the transfer was interrupted by a
* reboot.
*
* Note that this class is not thread safe.
*/
class TransferOwnershipMetadataManager {
final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner";
final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner";
@VisibleForTesting
final static String TAG_USER_ID = "user-id";
@VisibleForTesting
final static String TAG_SOURCE_COMPONENT = "source-component";
@VisibleForTesting
final static String TAG_TARGET_COMPONENT = "target-component";
@VisibleForTesting
final static String TAG_ADMIN_TYPE = "admin-type";
private final static String TAG = TransferOwnershipMetadataManager.class.getName();
public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml";
private final Injector mInjector;
TransferOwnershipMetadataManager() {
this(new Injector());
}
@VisibleForTesting
TransferOwnershipMetadataManager(Injector injector) {
mInjector = injector;
}
boolean saveMetadataFile(Metadata params) {
final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(),
OWNER_TRANSFER_METADATA_XML);
final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile);
FileOutputStream stream = null;
try {
stream = atomicFile.startWrite();
final XmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(stream, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId));
insertSimpleTag(serializer,
TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString());
insertSimpleTag(serializer,
TAG_TARGET_COMPONENT, params.targetComponent.flattenToString());
insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType);
serializer.endDocument();
atomicFile.finishWrite(stream);
return true;
} catch (IOException e) {
Slog.e(TAG, "Caught exception while trying to save Owner Transfer "
+ "Params to file " + transferOwnershipMetadataFile, e);
transferOwnershipMetadataFile.delete();
atomicFile.failWrite(stream);
}
return false;
}
private void insertSimpleTag(XmlSerializer serializer, String tagName, String value)
throws IOException {
serializer.startTag(null, tagName);
serializer.text(value);
serializer.endTag(null, tagName);
}
@Nullable
Metadata loadMetadataFile() {
final File transferOwnershipMetadataFile =
new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML);
if (!transferOwnershipMetadataFile.exists()) {
return null;
}
Slog.d(TAG, "Loading TransferOwnershipMetadataManager from "
+ transferOwnershipMetadataFile);
try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) {
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
return parseMetadataFile(parser);
} catch (IOException | XmlPullParserException | IllegalArgumentException e) {
Slog.e(TAG, "Caught exception while trying to load the "
+ "owner transfer params from file " + transferOwnershipMetadataFile, e);
}
return null;
}
private Metadata parseMetadataFile(XmlPullParser parser)
throws XmlPullParserException, IOException {
int type;
final int outerDepth = parser.getDepth();
int userId = 0;
String adminComponent = null;
String targetComponent = null;
String adminType = null;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
switch (parser.getName()) {
case TAG_USER_ID:
parser.next();
userId = Integer.parseInt(parser.getText());
break;
case TAG_TARGET_COMPONENT:
parser.next();
targetComponent = parser.getText();
break;
case TAG_SOURCE_COMPONENT:
parser.next();
adminComponent = parser.getText();
break;
case TAG_ADMIN_TYPE:
parser.next();
adminType = parser.getText();
break;
}
}
return new Metadata(adminComponent, targetComponent, userId, adminType);
}
void deleteMetadataFile() {
new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete();
}
boolean metadataFileExists() {
return new File(mInjector.getOwnerTransferMetadataDir(),
OWNER_TRANSFER_METADATA_XML).exists();
}
static class Metadata {
final int userId;
final ComponentName sourceComponent;
final ComponentName targetComponent;
final String adminType;
Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent,
@NonNull int userId, @NonNull String adminType) {
this.sourceComponent = sourceComponent;
this.targetComponent = targetComponent;
Preconditions.checkNotNull(sourceComponent);
Preconditions.checkNotNull(targetComponent);
Preconditions.checkStringNotEmpty(adminType);
this.userId = userId;
this.adminType = adminType;
}
Metadata(@NonNull String flatSourceComponent, @NonNull String flatTargetComponent,
@NonNull int userId, @NonNull String adminType) {
this(unflattenComponentUnchecked(flatSourceComponent),
unflattenComponentUnchecked(flatTargetComponent), userId, adminType);
}
private static ComponentName unflattenComponentUnchecked(String flatComponent) {
Preconditions.checkNotNull(flatComponent);
return ComponentName.unflattenFromString(flatComponent);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Metadata)) {
return false;
}
Metadata params = (Metadata) obj;
return userId == params.userId
&& sourceComponent.equals(params.sourceComponent)
&& targetComponent.equals(params.targetComponent)
&& TextUtils.equals(adminType, params.adminType);
}
@Override
public int hashCode() {
int hashCode = 1;
hashCode = 31 * hashCode + userId;
hashCode = 31 * hashCode + sourceComponent.hashCode();
hashCode = 31 * hashCode + targetComponent.hashCode();
hashCode = 31 * hashCode + adminType.hashCode();
return hashCode;
}
}
@VisibleForTesting
static class Injector {
public File getOwnerTransferMetadataDir() {
return Environment.getDataSystemDirectory();
}
}
}