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.Xml; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.FastXmlSerializer; 31 import com.android.internal.util.Preconditions; 32 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 import org.xmlpull.v1.XmlSerializer; 36 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.nio.charset.StandardCharsets; 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 XmlSerializer serializer = new FastXmlSerializer(); 87 serializer.setOutput(stream, StandardCharsets.UTF_8.name()); 88 serializer.startDocument(null, true); 89 insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId)); 90 insertSimpleTag(serializer, 91 TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString()); 92 insertSimpleTag(serializer, 93 TAG_TARGET_COMPONENT, params.targetComponent.flattenToString()); 94 insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType); 95 serializer.endDocument(); 96 atomicFile.finishWrite(stream); 97 return true; 98 } catch (IOException e) { 99 Slog.e(TAG, "Caught exception while trying to save Owner Transfer " 100 + "Params to file " + transferOwnershipMetadataFile, e); 101 transferOwnershipMetadataFile.delete(); 102 atomicFile.failWrite(stream); 103 } 104 return false; 105 } 106 insertSimpleTag(XmlSerializer serializer, String tagName, String value)107 private void insertSimpleTag(XmlSerializer serializer, String tagName, String value) 108 throws IOException { 109 serializer.startTag(null, tagName); 110 serializer.text(value); 111 serializer.endTag(null, tagName); 112 } 113 114 @Nullable loadMetadataFile()115 Metadata loadMetadataFile() { 116 final File transferOwnershipMetadataFile = 117 new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML); 118 if (!transferOwnershipMetadataFile.exists()) { 119 return null; 120 } 121 Slog.d(TAG, "Loading TransferOwnershipMetadataManager from " 122 + transferOwnershipMetadataFile); 123 try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) { 124 final XmlPullParser parser = Xml.newPullParser(); 125 parser.setInput(stream, null); 126 return parseMetadataFile(parser); 127 } catch (IOException | XmlPullParserException | IllegalArgumentException e) { 128 Slog.e(TAG, "Caught exception while trying to load the " 129 + "owner transfer params from file " + transferOwnershipMetadataFile, e); 130 } 131 return null; 132 } 133 parseMetadataFile(XmlPullParser parser)134 private Metadata parseMetadataFile(XmlPullParser parser) 135 throws XmlPullParserException, IOException { 136 int type; 137 final int outerDepth = parser.getDepth(); 138 int userId = 0; 139 String adminComponent = null; 140 String targetComponent = null; 141 String adminType = null; 142 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 143 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 144 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 145 continue; 146 } 147 switch (parser.getName()) { 148 case TAG_USER_ID: 149 parser.next(); 150 userId = Integer.parseInt(parser.getText()); 151 break; 152 case TAG_TARGET_COMPONENT: 153 parser.next(); 154 targetComponent = parser.getText(); 155 break; 156 case TAG_SOURCE_COMPONENT: 157 parser.next(); 158 adminComponent = parser.getText(); 159 break; 160 case TAG_ADMIN_TYPE: 161 parser.next(); 162 adminType = parser.getText(); 163 break; 164 } 165 } 166 return new Metadata(adminComponent, targetComponent, userId, adminType); 167 } 168 deleteMetadataFile()169 void deleteMetadataFile() { 170 new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete(); 171 } 172 metadataFileExists()173 boolean metadataFileExists() { 174 return new File(mInjector.getOwnerTransferMetadataDir(), 175 OWNER_TRANSFER_METADATA_XML).exists(); 176 } 177 178 static class Metadata { 179 final int userId; 180 final ComponentName sourceComponent; 181 final ComponentName targetComponent; 182 final String adminType; 183 Metadata(@onNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, @NonNull int userId, @NonNull String adminType)184 Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, 185 @NonNull int userId, @NonNull String adminType) { 186 this.sourceComponent = sourceComponent; 187 this.targetComponent = targetComponent; 188 Preconditions.checkNotNull(sourceComponent); 189 Preconditions.checkNotNull(targetComponent); 190 Preconditions.checkStringNotEmpty(adminType); 191 this.userId = userId; 192 this.adminType = adminType; 193 } 194 Metadata(@onNull String flatSourceComponent, @NonNull String flatTargetComponent, @NonNull int userId, @NonNull String adminType)195 Metadata(@NonNull String flatSourceComponent, @NonNull String flatTargetComponent, 196 @NonNull int userId, @NonNull String adminType) { 197 this(unflattenComponentUnchecked(flatSourceComponent), 198 unflattenComponentUnchecked(flatTargetComponent), userId, adminType); 199 } 200 unflattenComponentUnchecked(String flatComponent)201 private static ComponentName unflattenComponentUnchecked(String flatComponent) { 202 Preconditions.checkNotNull(flatComponent); 203 return ComponentName.unflattenFromString(flatComponent); 204 } 205 206 @Override equals(Object obj)207 public boolean equals(Object obj) { 208 if (!(obj instanceof Metadata)) { 209 return false; 210 } 211 Metadata params = (Metadata) obj; 212 213 return userId == params.userId 214 && sourceComponent.equals(params.sourceComponent) 215 && targetComponent.equals(params.targetComponent) 216 && TextUtils.equals(adminType, params.adminType); 217 } 218 219 @Override hashCode()220 public int hashCode() { 221 int hashCode = 1; 222 hashCode = 31 * hashCode + userId; 223 hashCode = 31 * hashCode + sourceComponent.hashCode(); 224 hashCode = 31 * hashCode + targetComponent.hashCode(); 225 hashCode = 31 * hashCode + adminType.hashCode(); 226 return hashCode; 227 } 228 } 229 230 @VisibleForTesting 231 static class Injector { getOwnerTransferMetadataDir()232 public File getOwnerTransferMetadataDir() { 233 return Environment.getDataSystemDirectory(); 234 } 235 } 236 } 237