1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.devicepolicy; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.ComponentName; 23 import android.os.Environment; 24 import android.text.TextUtils; 25 import android.util.AtomicFile; 26 import android.util.Slog; 27 import android.util.TypedXmlPullParser; 28 import android.util.TypedXmlSerializer; 29 import android.util.Xml; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.Preconditions; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.util.Objects; 42 43 /** 44 * Handles reading and writing of the owner transfer metadata file. 45 * 46 * Before we perform a device or profile owner transfer, we save this xml file with information 47 * about the current admin, target admin, user id and admin type (device owner or profile owner). 48 * After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after 49 * device boot the file is still there, this indicates that the transfer was interrupted by a 50 * reboot. 51 * 52 * Note that this class is not thread safe. 53 */ 54 class TransferOwnershipMetadataManager { 55 final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner"; 56 final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner"; 57 @VisibleForTesting 58 final static String TAG_USER_ID = "user-id"; 59 @VisibleForTesting 60 final static String TAG_SOURCE_COMPONENT = "source-component"; 61 @VisibleForTesting 62 final static String TAG_TARGET_COMPONENT = "target-component"; 63 @VisibleForTesting 64 final static String TAG_ADMIN_TYPE = "admin-type"; 65 private final static String TAG = TransferOwnershipMetadataManager.class.getName(); 66 public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml"; 67 68 private final Injector mInjector; 69 TransferOwnershipMetadataManager()70 TransferOwnershipMetadataManager() { 71 this(new Injector()); 72 } 73 74 @VisibleForTesting TransferOwnershipMetadataManager(Injector injector)75 TransferOwnershipMetadataManager(Injector injector) { 76 mInjector = injector; 77 } 78 saveMetadataFile(Metadata params)79 boolean saveMetadataFile(Metadata params) { 80 final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(), 81 OWNER_TRANSFER_METADATA_XML); 82 final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile); 83 FileOutputStream stream = null; 84 try { 85 stream = atomicFile.startWrite(); 86 final TypedXmlSerializer serializer = Xml.resolveSerializer(stream); 87 serializer.startDocument(null, true); 88 insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId)); 89 insertSimpleTag(serializer, 90 TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString()); 91 insertSimpleTag(serializer, 92 TAG_TARGET_COMPONENT, params.targetComponent.flattenToString()); 93 insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType); 94 serializer.endDocument(); 95 atomicFile.finishWrite(stream); 96 return true; 97 } catch (IOException e) { 98 Slog.e(TAG, "Caught exception while trying to save Owner Transfer " 99 + "Params to file " + transferOwnershipMetadataFile, e); 100 transferOwnershipMetadataFile.delete(); 101 atomicFile.failWrite(stream); 102 } 103 return false; 104 } 105 insertSimpleTag(TypedXmlSerializer serializer, String tagName, String value)106 private void insertSimpleTag(TypedXmlSerializer serializer, String tagName, String value) 107 throws IOException { 108 serializer.startTag(null, tagName); 109 serializer.text(value); 110 serializer.endTag(null, tagName); 111 } 112 113 @Nullable loadMetadataFile()114 Metadata loadMetadataFile() { 115 final File transferOwnershipMetadataFile = 116 new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML); 117 if (!transferOwnershipMetadataFile.exists()) { 118 return null; 119 } 120 Slog.d(TAG, "Loading TransferOwnershipMetadataManager from " 121 + transferOwnershipMetadataFile); 122 try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) { 123 final TypedXmlPullParser parser = Xml.resolvePullParser(stream); 124 return parseMetadataFile(parser); 125 } catch (IOException | XmlPullParserException | IllegalArgumentException e) { 126 Slog.e(TAG, "Caught exception while trying to load the " 127 + "owner transfer params from file " + transferOwnershipMetadataFile, e); 128 } 129 return null; 130 } 131 parseMetadataFile(TypedXmlPullParser parser)132 private Metadata parseMetadataFile(TypedXmlPullParser parser) 133 throws XmlPullParserException, IOException { 134 int type; 135 final int outerDepth = parser.getDepth(); 136 int userId = 0; 137 String adminComponent = null; 138 String targetComponent = null; 139 String adminType = null; 140 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 141 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 142 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 143 continue; 144 } 145 switch (parser.getName()) { 146 case TAG_USER_ID: 147 parser.next(); 148 userId = Integer.parseInt(parser.getText()); 149 break; 150 case TAG_TARGET_COMPONENT: 151 parser.next(); 152 targetComponent = parser.getText(); 153 break; 154 case TAG_SOURCE_COMPONENT: 155 parser.next(); 156 adminComponent = parser.getText(); 157 break; 158 case TAG_ADMIN_TYPE: 159 parser.next(); 160 adminType = parser.getText(); 161 break; 162 } 163 } 164 return new Metadata(adminComponent, targetComponent, userId, adminType); 165 } 166 deleteMetadataFile()167 void deleteMetadataFile() { 168 new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete(); 169 } 170 metadataFileExists()171 boolean metadataFileExists() { 172 return new File(mInjector.getOwnerTransferMetadataDir(), 173 OWNER_TRANSFER_METADATA_XML).exists(); 174 } 175 176 static class Metadata { 177 final int userId; 178 final ComponentName sourceComponent; 179 final ComponentName targetComponent; 180 final String adminType; 181 Metadata(@onNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, @NonNull int userId, @NonNull String adminType)182 Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, 183 @NonNull int userId, @NonNull String adminType) { 184 this.sourceComponent = sourceComponent; 185 this.targetComponent = targetComponent; 186 Objects.requireNonNull(sourceComponent); 187 Objects.requireNonNull(targetComponent); 188 Preconditions.checkStringNotEmpty(adminType); 189 this.userId = userId; 190 this.adminType = adminType; 191 } 192 Metadata(@onNull String flatSourceComponent, @NonNull String flatTargetComponent, @NonNull int userId, @NonNull String adminType)193 Metadata(@NonNull String flatSourceComponent, @NonNull String flatTargetComponent, 194 @NonNull int userId, @NonNull String adminType) { 195 this(unflattenComponentUnchecked(flatSourceComponent), 196 unflattenComponentUnchecked(flatTargetComponent), userId, adminType); 197 } 198 unflattenComponentUnchecked(String flatComponent)199 private static ComponentName unflattenComponentUnchecked(String flatComponent) { 200 Objects.requireNonNull(flatComponent); 201 return ComponentName.unflattenFromString(flatComponent); 202 } 203 204 @Override equals(Object obj)205 public boolean equals(Object obj) { 206 if (!(obj instanceof Metadata)) { 207 return false; 208 } 209 Metadata params = (Metadata) obj; 210 211 return userId == params.userId 212 && sourceComponent.equals(params.sourceComponent) 213 && targetComponent.equals(params.targetComponent) 214 && TextUtils.equals(adminType, params.adminType); 215 } 216 217 @Override hashCode()218 public int hashCode() { 219 int hashCode = 1; 220 hashCode = 31 * hashCode + userId; 221 hashCode = 31 * hashCode + sourceComponent.hashCode(); 222 hashCode = 31 * hashCode + targetComponent.hashCode(); 223 hashCode = 31 * hashCode + adminType.hashCode(); 224 return hashCode; 225 } 226 } 227 228 @VisibleForTesting 229 static class Injector { getOwnerTransferMetadataDir()230 public File getOwnerTransferMetadataDir() { 231 return Environment.getDataSystemDirectory(); 232 } 233 } 234 } 235