• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014, 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;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.ComponentInfo;
25 import android.content.pm.IPackageDeleteObserver;
26 import android.content.pm.IPackageManager;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.res.Resources;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.util.Xml;
35 import android.view.inputmethod.InputMethodInfo;
36 import android.view.inputmethod.InputMethodManager;
37 
38 import com.android.internal.util.FastXmlSerializer;
39 import com.android.managedprovisioning.ProvisionLogger;
40 import com.android.managedprovisioning.R;
41 import com.android.managedprovisioning.Utils;
42 
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Set;
54 import java.util.concurrent.atomic.AtomicInteger;
55 
56 import org.xmlpull.v1.XmlPullParser;
57 import org.xmlpull.v1.XmlPullParserException;
58 import org.xmlpull.v1.XmlSerializer;
59 
60 /**
61  * Deletes all system apps with a launcher that are not in the required set of packages.
62  * Furthermore deletes all disallowed apps.
63  *
64  * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as
65  * required.
66  *
67  * This task may be run when a profile (both for managed device and managed profile) is created.
68  * In that case the newProfile flag should be true.
69  *
70  * It should also be run after a system update with newProfile false, if
71  * {@link #shouldDeleteNonRequiredApps} returns true. Note that only newly installed system apps
72  * will be deleted.
73  */
74 public class DeleteNonRequiredAppsTask {
75     private final Callback mCallback;
76     private final Context mContext;
77     private final String mMdmPackageName;
78     private final IPackageManager mIpm;
79     private final PackageManager mPm;
80     private final List<String> mRequiredAppsList;
81     private final List<String> mDisallowedAppsList;
82     private final List<String> mVendorRequiredAppsList;
83     private final List<String> mVendorDisallowedAppsList;
84     private final int mUserId;
85     private final int mProvisioningType;
86     private final boolean mNewProfile; // If we are provisioning a new managed profile/device.
87     private final boolean mLeaveAllSystemAppsEnabled;
88 
89     private static final String TAG_SYSTEM_APPS = "system-apps";
90     private static final String TAG_PACKAGE_LIST_ITEM = "item";
91     private static final String ATTR_VALUE = "value";
92 
93     public static final int DEVICE_OWNER = 0;
94     public static final int PROFILE_OWNER = 1;
95 
96     /**
97      * Provisioning type should be either {@link #DEVICE_OWNER} or {@link #PROFILE_OWNER}.
98      **/
DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int provisioningType, boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback)99     public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int provisioningType,
100             boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback) {
101 
102         mCallback = callback;
103         mContext = context;
104         mMdmPackageName = mdmPackageName;
105         mProvisioningType = provisioningType;
106         mUserId = userId;
107         mNewProfile = newProfile;
108         mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
109         mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
110         mPm = context.getPackageManager();
111 
112         int requiredAppsListArray;
113         int vendorRequiredAppsListArray;
114         int disallowedAppsListArray;
115         int vendorDisallowedAppsListArray;
116         if (mProvisioningType == DEVICE_OWNER) {
117             requiredAppsListArray = R.array.required_apps_managed_device;
118             disallowedAppsListArray = R.array.disallowed_apps_managed_device;
119             vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device;
120             vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device;
121         } else if (mProvisioningType == PROFILE_OWNER) {
122             requiredAppsListArray = R.array.required_apps_managed_profile;
123             disallowedAppsListArray = R.array.disallowed_apps_managed_profile;
124             vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_profile;
125             vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_profile;
126         } else {
127             throw new IllegalArgumentException("Provisioning type " + mProvisioningType +
128                     " not supported.");
129         }
130 
131         Resources resources = mContext.getResources();
132         mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray));
133         mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray));
134         mVendorRequiredAppsList = Arrays.asList(
135                 resources.getStringArray(vendorRequiredAppsListArray));
136         mVendorDisallowedAppsList = Arrays.asList(
137                 resources.getStringArray(vendorDisallowedAppsListArray));
138     }
139 
run()140     public void run() {
141         if (mLeaveAllSystemAppsEnabled) {
142             ProvisionLogger.logd("Not deleting non-required apps.");
143             mCallback.onSuccess();
144             return;
145         }
146         ProvisionLogger.logd("Deleting non required apps.");
147 
148         File systemAppsFile = getSystemAppsFile(mContext, mUserId);
149         systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
150 
151         Set<String> currentApps = Utils.getCurrentSystemApps(mUserId);
152         Set<String> previousApps;
153         if (mNewProfile) {
154             // Provisioning case.
155             previousApps = new HashSet<String>();
156         } else {
157             // OTA case.
158             if (!systemAppsFile.exists()) {
159                 ProvisionLogger.loge("Could not find the system apps file " +
160                         systemAppsFile.getAbsolutePath());
161                 mCallback.onError();
162                 return;
163             }
164             previousApps = readSystemApps(systemAppsFile);
165         }
166 
167         writeSystemApps(currentApps, systemAppsFile);
168         Set<String> newApps = currentApps;
169         newApps.removeAll(previousApps);
170 
171         // Newly installed system apps are uninstalled when they are not required and are either
172         // disallowed or have a launcher icon.
173         Set<String> packagesToDelete = newApps;
174         packagesToDelete.removeAll(getRequiredApps());
175         Set<String> packagesToRetain = getCurrentAppsWithLauncher();
176         // Don't delete the system input method packages in case of Device owner provisioning.
177         if (mProvisioningType == DEVICE_OWNER) {
178             packagesToRetain.removeAll(getSystemInputMethods());
179         }
180         packagesToRetain.addAll(getDisallowedApps());
181         packagesToDelete.retainAll(packagesToRetain);
182 
183         if (packagesToDelete.isEmpty()) {
184             mCallback.onSuccess();
185             return;
186         }
187         removeNonInstalledPackages(packagesToDelete);
188 
189         PackageDeleteObserver packageDeleteObserver =
190                 new PackageDeleteObserver(packagesToDelete.size());
191         for (String packageName : packagesToDelete) {
192             try {
193                 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
194                         PackageManager.DELETE_SYSTEM_APP);
195             } catch (RemoteException neverThrown) {
196                 // Never thrown, as we are making local calls.
197                 ProvisionLogger.loge("This should not happen.", neverThrown);
198             }
199         }
200     }
201 
202     /**
203      * Remove all packages from the set that are not installed.
204      */
removeNonInstalledPackages(Set<String> packages)205     private void removeNonInstalledPackages(Set<String> packages) {
206         Set<String> toBeRemoved = new HashSet<String>();
207         for (String packageName : packages) {
208             try {
209                 PackageInfo info = mIpm.getPackageInfo(packageName, 0 /* default flags */, mUserId);
210                 if (info == null) {
211                     toBeRemoved.add(packageName);
212                 }
213             } catch (RemoteException neverThrown) {
214                 // Never thrown, as we are making local calls.
215                 ProvisionLogger.loge("This should not happen.", neverThrown);
216             }
217         }
218         packages.removeAll(toBeRemoved);
219     }
220 
221     /**
222      * Returns if this task should be run on OTA.
223      * This is indicated by the presence of the system apps file.
224      */
shouldDeleteNonRequiredApps(Context context, int userId)225     public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
226         return getSystemAppsFile(context, userId).exists();
227     }
228 
getSystemAppsFile(Context context, int userId)229     static File getSystemAppsFile(Context context, int userId) {
230         return new File(context.getFilesDir() + File.separator + "system_apps"
231                 + File.separator + "user" + userId + ".xml");
232     }
233 
getCurrentAppsWithLauncher()234     private Set<String> getCurrentAppsWithLauncher() {
235         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
236         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
237         List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
238                 PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS,
239                 mUserId);
240         Set<String> apps = new HashSet<String>();
241         for (ResolveInfo resolveInfo : resolveInfos) {
242             apps.add(resolveInfo.activityInfo.packageName);
243         }
244         return apps;
245     }
246 
getSystemInputMethods()247     private Set<String> getSystemInputMethods() {
248         final InputMethodManager inputMethodManager =
249                 (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
250         List<InputMethodInfo> inputMethods = inputMethodManager.getInputMethodList();
251         Set<String> systemInputMethods = new HashSet<String>();
252         for (InputMethodInfo inputMethodInfo : inputMethods) {
253             ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
254             if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
255                 systemInputMethods.add(inputMethodInfo.getPackageName());
256             }
257         }
258         return systemInputMethods;
259     }
260 
writeSystemApps(Set<String> packageNames, File systemAppsFile)261     private void writeSystemApps(Set<String> packageNames, File systemAppsFile) {
262         try {
263             FileOutputStream stream = new FileOutputStream(systemAppsFile, false);
264             XmlSerializer serializer = new FastXmlSerializer();
265             serializer.setOutput(stream, "utf-8");
266             serializer.startDocument(null, true);
267             serializer.startTag(null, TAG_SYSTEM_APPS);
268             for (String packageName : packageNames) {
269                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
270                 serializer.attribute(null, ATTR_VALUE, packageName);
271                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
272             }
273             serializer.endTag(null, TAG_SYSTEM_APPS);
274             serializer.endDocument();
275             stream.close();
276         } catch (IOException e) {
277             ProvisionLogger.loge("IOException trying to write the system apps", e);
278         }
279     }
280 
readSystemApps(File systemAppsFile)281     private Set<String> readSystemApps(File systemAppsFile) {
282         Set<String> result = new HashSet<String>();
283         if (!systemAppsFile.exists()) {
284             return result;
285         }
286         try {
287             FileInputStream stream = new FileInputStream(systemAppsFile);
288 
289             XmlPullParser parser = Xml.newPullParser();
290             parser.setInput(stream, null);
291 
292             int type = parser.next();
293             int outerDepth = parser.getDepth();
294             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
295                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
296                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
297                     continue;
298                 }
299                 String tag = parser.getName();
300                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
301                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
302                 } else {
303                     ProvisionLogger.loge("Unknown tag: " + tag);
304                 }
305             }
306             stream.close();
307         } catch (IOException e) {
308             ProvisionLogger.loge("IOException trying to read the system apps", e);
309         } catch (XmlPullParserException e) {
310             ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
311         }
312         return result;
313     }
314 
getRequiredApps()315     protected Set<String> getRequiredApps() {
316         HashSet<String> requiredApps = new HashSet<String>();
317         requiredApps.addAll(mRequiredAppsList);
318         requiredApps.addAll(mVendorRequiredAppsList);
319         requiredApps.add(mMdmPackageName);
320         return requiredApps;
321     }
322 
getDisallowedApps()323     private Set<String> getDisallowedApps() {
324         HashSet<String> disallowedApps = new HashSet<String>();
325         disallowedApps.addAll(mDisallowedAppsList);
326         disallowedApps.addAll(mVendorDisallowedAppsList);
327         return disallowedApps;
328     }
329 
330     /**
331      * Runs the next task when all packages have been deleted or shuts down the activity if package
332      * deletion fails.
333      */
334     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
335         private final AtomicInteger mPackageCount = new AtomicInteger(0);
336 
PackageDeleteObserver(int packageCount)337         public PackageDeleteObserver(int packageCount) {
338             this.mPackageCount.set(packageCount);
339         }
340 
341         @Override
packageDeleted(String packageName, int returnCode)342         public void packageDeleted(String packageName, int returnCode) {
343             if (returnCode != PackageManager.DELETE_SUCCEEDED) {
344                 ProvisionLogger.logw(
345                         "Could not finish the provisioning: package deletion failed");
346                 mCallback.onError();
347                 return;
348             }
349             int currentPackageCount = mPackageCount.decrementAndGet();
350             if (currentPackageCount == 0) {
351                 ProvisionLogger.logi("All non-required system apps with launcher icon, "
352                         + "and all disallowed apps have been uninstalled.");
353                 mCallback.onSuccess();
354             }
355         }
356     }
357 
358     public abstract static class Callback {
onSuccess()359         public abstract void onSuccess();
onError()360         public abstract void onError();
361     }
362 }
363