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