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.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      **/
     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 
    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      */
    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      */
    225     public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
    226         return getSystemAppsFile(context, userId).exists();
    227     }
    228 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    337         public PackageDeleteObserver(int packageCount) {
    338             this.mPackageCount.set(packageCount);
    339         }
    340 
    341         @Override
    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 {
    359         public abstract void onSuccess();
    360         public abstract void onError();
    361     }
    362 }
    363