/* * 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(); } } }