Home | History | Annotate | Download | only in task
      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      **/
    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
    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 
    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 
    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 
    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      */
    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      */
    252     public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
    253         return getSystemAppsFile(context, userId).exists();
    254     }
    255 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    373         public PackageDeleteObserver(int packageCount) {
    374             this.mPackageCount.set(packageCount);
    375         }
    376 
    377         @Override
    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 
    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 {
    400         public abstract void onSuccess();
    401         public abstract void onError();
    402     }
    403 }
    404