• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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