1 /*
2  * Copyright 2020, 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.managedprovisioning.task.interactacrossprofiles;
18 
19 
20 import android.app.admin.DevicePolicyManager;
21 import android.content.Context;
22 import android.os.UserManager;
23 import android.util.Xml;
24 
25 import com.android.internal.util.FastXmlSerializer;
26 import com.android.managedprovisioning.common.ProvisionLogger;
27 
28 import org.xmlpull.v1.XmlPullParser;
29 import org.xmlpull.v1.XmlPullParserException;
30 import org.xmlpull.v1.XmlSerializer;
31 
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.util.HashSet;
37 import java.util.Set;
38 
39 /**
40  * Stores and retrieves the cross-profile apps whitelist during provisioning and on
41  * subsequent OTAs.
42  */
43 public class CrossProfileAppsSnapshot {
44     private static final String TAG_CROSS_PROFILE_APPS = "cross-profile-apps";
45     private static final String TAG_PACKAGE_LIST_ITEM = "item";
46     private static final String ATTR_VALUE = "value";
47     private static final String FOLDER_NAME = "cross_profile_apps";
48 
49     private final Context mContext;
50 
CrossProfileAppsSnapshot(Context context)51     public CrossProfileAppsSnapshot(Context context) {
52         if (context == null) {
53             throw new NullPointerException();
54         }
55         mContext = context;
56     }
57 
58     /**
59      * Returns whether currently a snapshot exists for the given user.
60      *
61      * @param userId the user id for which the snapshot is requested.
62      */
hasSnapshot(int userId)63     public boolean hasSnapshot(int userId) {
64         return getCrossProfileAppsFile(mContext, userId).exists();
65     }
66 
67     /**
68      * Returns the last stored snapshot for the given user.
69      *
70      * @param userId the user id for which the snapshot is requested.
71      */
getSnapshot(int userId)72     public Set<String> getSnapshot(int userId) {
73         return readCrossProfileApps(getCrossProfileAppsFile(mContext, userId));
74     }
75 
76     /**
77      * Call this method to take a snapshot of the current cross profile apps whitelist.
78      *
79      * @param userId the user id for which the snapshot should be taken.
80      */
takeNewSnapshot(int userId)81     public void takeNewSnapshot(int userId) {
82         final File systemAppsFile = getCrossProfileAppsFile(mContext, userId);
83         systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
84         writeCrossProfileApps(getCurrentCrossProfileAppsWhitelist(), systemAppsFile);
85     }
86 
getCurrentCrossProfileAppsWhitelist()87     private Set<String> getCurrentCrossProfileAppsWhitelist() {
88         DevicePolicyManager devicePolicyManager =
89                 mContext.getSystemService(DevicePolicyManager.class);
90         return devicePolicyManager.getDefaultCrossProfilePackages();
91     }
92 
writeCrossProfileApps(Set<String> packageNames, File crossProfileAppsFile)93     private void writeCrossProfileApps(Set<String> packageNames, File crossProfileAppsFile) {
94         try {
95             FileOutputStream stream = new FileOutputStream(crossProfileAppsFile, false);
96             XmlSerializer serializer = new FastXmlSerializer();
97             serializer.setOutput(stream, "utf-8");
98             serializer.startDocument(null, true);
99             serializer.startTag(null, TAG_CROSS_PROFILE_APPS);
100             for (String packageName : packageNames) {
101                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
102                 serializer.attribute(null, ATTR_VALUE, packageName);
103                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
104             }
105             serializer.endTag(null, TAG_CROSS_PROFILE_APPS);
106             serializer.endDocument();
107             stream.close();
108         } catch (IOException e) {
109             ProvisionLogger.loge("IOException trying to write the cross profile apps", e);
110         }
111     }
112 
readCrossProfileApps(File systemAppsFile)113     private Set<String> readCrossProfileApps(File systemAppsFile) {
114         Set<String> result = new HashSet<>();
115         if (!systemAppsFile.exists()) {
116             return result;
117         }
118         try {
119             FileInputStream stream = new FileInputStream(systemAppsFile);
120 
121             XmlPullParser parser = Xml.newPullParser();
122             parser.setInput(stream, null);
123             parser.next();
124 
125             int type;
126             int outerDepth = parser.getDepth();
127             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
128                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
129                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
130                     continue;
131                 }
132                 String tag = parser.getName();
133                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
134                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
135                 } else {
136                     ProvisionLogger.loge("Unknown tag: " + tag);
137                 }
138             }
139             stream.close();
140         } catch (IOException e) {
141             ProvisionLogger.loge("IOException trying to read the cross profile apps", e);
142         } catch (XmlPullParserException e) {
143             ProvisionLogger.loge("XmlPullParserException trying to read the cross profile apps", e);
144         }
145         return result;
146     }
147 
getCrossProfileAppsFile(Context context, int userId)148     private static File getCrossProfileAppsFile(Context context, int userId) {
149         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
150         int userSerialNumber = userManager.getUserSerialNumber(userId);
151         if (userSerialNumber == -1 ) {
152             throw new IllegalArgumentException("Invalid userId : " + userId);
153         }
154         return new File(getFolder(context), userSerialNumber + ".xml");
155     }
156 
getFolder(Context context)157     private static File getFolder(Context context) {
158         return new File(context.getFilesDir(), FOLDER_NAME);
159     }
160 }
161