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.PackageManager.NameNotFoundException;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.ResolveInfo;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.util.Xml;
     33 
     34 import com.android.internal.util.FastXmlSerializer;
     35 import com.android.managedprovisioning.ProvisionLogger;
     36 import com.android.managedprovisioning.R;
     37 
     38 import java.io.File;
     39 import java.io.FileInputStream;
     40 import java.io.FileOutputStream;
     41 import java.io.IOException;
     42 
     43 import java.util.ArrayList;
     44 import java.util.Arrays;
     45 import java.util.HashSet;
     46 import java.util.List;
     47 import java.util.Set;
     48 import java.util.concurrent.atomic.AtomicInteger;
     49 
     50 import org.xmlpull.v1.XmlPullParser;
     51 import org.xmlpull.v1.XmlPullParserException;
     52 import org.xmlpull.v1.XmlSerializer;
     53 
     54 /**
     55  * Removes all system apps with a launcher that are not required.
     56  * Also disables sharing via Bluetooth, and components that listen to
     57  * ACTION_INSTALL_SHORTCUT.
     58  * This class is called a first time when a user is created, but also after a system update.
     59  * In this case, it checks if the system apps that have been added need to be disabled.
     60  */
     61 public class DeleteNonRequiredAppsTask {
     62     private final Callback mCallback;
     63     private final Context mContext;
     64     private final IPackageManager mIpm;
     65     private final String mMdmPackageName;
     66     private final PackageManager mPm;
     67     private final int mReqAppsList;
     68     private final int mVendorReqAppsList;
     69     private final int mUserId;
     70     private final boolean mNewProfile; // If we are provisioning a new managed profile/device.
     71     private final boolean mDisableInstallShortcutListenersAndTelecom;
     72 
     73     private static final String TAG_SYSTEM_APPS = "system-apps";
     74     private static final String TAG_PACKAGE_LIST_ITEM = "item";
     75     private static final String ATTR_VALUE = "value";
     76 
     77     public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId,
     78             int requiredAppsList, int vendorRequiredAppsList, boolean newProfile,
     79             boolean disableInstallShortcutListenersAndTelecom, Callback callback) {
     80         mCallback = callback;
     81         mContext = context;
     82         mMdmPackageName = mdmPackageName;
     83         mUserId = userId;
     84         mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     85         mPm = context.getPackageManager();
     86         mReqAppsList = requiredAppsList;
     87         mVendorReqAppsList = vendorRequiredAppsList;
     88         mNewProfile = newProfile;
     89         mDisableInstallShortcutListenersAndTelecom = disableInstallShortcutListenersAndTelecom;
     90     }
     91 
     92     public void run() {
     93         if (mNewProfile) {
     94             disableBluetoothSharing();
     95         }
     96         deleteNonRequiredApps();
     97     }
     98 
     99     /**
    100      * Returns if this task should be run on OTA.
    101      * This is indicated by the presence of the system apps file.
    102      */
    103     public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
    104         return getSystemAppsFile(context, userId).exists();
    105     }
    106 
    107     private void disableBluetoothSharing() {
    108         ProvisionLogger.logd("Disabling Bluetooth sharing.");
    109         disableComponent(new ComponentName("com.android.bluetooth",
    110                 "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
    111     }
    112 
    113     private void deleteNonRequiredApps() {
    114         ProvisionLogger.logd("Deleting non required apps.");
    115 
    116         File systemAppsFile = getSystemAppsFile(mContext, mUserId);
    117         systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
    118 
    119         Set<String> currentApps = getCurrentSystemApps();
    120         Set<String> previousApps;
    121         if (mNewProfile) {
    122             // Provisioning case.
    123 
    124             // If this userId was a managed profile before, file may exist. In this case, we ignore
    125             // what is in this file.
    126             previousApps = new HashSet<String>();
    127         } else {
    128             // OTA case.
    129 
    130             if (!systemAppsFile.exists()) {
    131                 // Error, this task should not have been run.
    132                 ProvisionLogger.loge("No system apps list found for user " + mUserId);
    133                 mCallback.onError();
    134                 return;
    135             }
    136 
    137             previousApps = readSystemApps(systemAppsFile);
    138         }
    139         writeSystemApps(currentApps, systemAppsFile);
    140         Set<String> newApps = currentApps;
    141         newApps.removeAll(previousApps);
    142 
    143         if (mDisableInstallShortcutListenersAndTelecom) {
    144             Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    145             if (previousApps.isEmpty()) {
    146                 // Here, all the apps are in newApps.
    147                 // It is faster to do it this way than to go through all the apps one by one.
    148                 disableReceivers(actionShortcut);
    149             } else {
    150                 // Here, all the apps are not in newApps. So we have to go through all the new
    151                 // apps one by one.
    152                 for (String newApp : newApps) {
    153                     actionShortcut.setPackage(newApp);
    154                     disableReceivers(actionShortcut);
    155                 }
    156             }
    157         }
    158         Set<String> packagesToDelete = newApps;
    159         packagesToDelete.removeAll(getRequiredApps());
    160         packagesToDelete.retainAll(getCurrentAppsWithLauncher());
    161         // com.android.server.telecom should not handle CALL intents in the managed profile.
    162         if (mDisableInstallShortcutListenersAndTelecom && mNewProfile) {
    163             packagesToDelete.add("com.android.server.telecom");
    164         }
    165         if (packagesToDelete.isEmpty()) {
    166             mCallback.onSuccess();
    167             return;
    168         }
    169         PackageDeleteObserver packageDeleteObserver =
    170                 new PackageDeleteObserver(packagesToDelete.size());
    171         for (String packageName : packagesToDelete) {
    172             try {
    173                 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
    174                         PackageManager.DELETE_SYSTEM_APP);
    175             } catch (RemoteException neverThrown) {
    176                     // Never thrown, as we are making local calls.
    177                 ProvisionLogger.loge("This should not happen.", neverThrown);
    178             }
    179         }
    180     }
    181 
    182     static File getSystemAppsFile(Context context, int userId) {
    183         return new File(context.getFilesDir() + File.separator + "system_apps"
    184                 + File.separator + "user" + userId + ".xml");
    185     }
    186 
    187     /**
    188      * Disable all components that can handle the specified broadcast intent.
    189      */
    190     private void disableReceivers(Intent intent) {
    191         List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId);
    192         for (ResolveInfo ri : receivers) {
    193             // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which
    194             // one.
    195             ComponentInfo ci;
    196             if (ri.activityInfo != null) {
    197                 ci = ri.activityInfo;
    198             } else if (ri.serviceInfo != null) {
    199                 ci = ri.serviceInfo;
    200             } else {
    201                 ci = ri.providerInfo;
    202             }
    203             disableComponent(new ComponentName(ci.packageName, ci.name));
    204         }
    205     }
    206 
    207     private void disableComponent(ComponentName toDisable) {
    208         try {
    209             mIpm.setComponentEnabledSetting(toDisable,
    210                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP,
    211                     mUserId);
    212         } catch (RemoteException neverThrown) {
    213             ProvisionLogger.loge("This should not happen.", neverThrown);
    214         } catch (Exception e) {
    215             ProvisionLogger.logw("Component not found, not disabling it: "
    216                 + toDisable.toShortString());
    217         }
    218     }
    219 
    220     /**
    221      * Returns the set of package names of apps that are in the system image,
    222      * whether they have been deleted or not.
    223      */
    224     private Set<String> getCurrentSystemApps() {
    225         Set<String> apps = new HashSet<String>();
    226         List<ApplicationInfo> aInfos = null;
    227         try {
    228             aInfos = mIpm.getInstalledApplications(
    229                     PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
    230         } catch (RemoteException neverThrown) {
    231             // Never thrown, as we are making local calls.
    232             ProvisionLogger.loge("This should not happen.", neverThrown);
    233         }
    234         for (ApplicationInfo aInfo : aInfos) {
    235             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    236                 apps.add(aInfo.packageName);
    237             }
    238         }
    239         return apps;
    240     }
    241 
    242     private Set<String> getCurrentAppsWithLauncher() {
    243         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
    244         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    245         List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
    246                 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId);
    247         Set<String> apps = new HashSet<String>();
    248         for (ResolveInfo resolveInfo : resolveInfos) {
    249             apps.add(resolveInfo.activityInfo.packageName);
    250         }
    251         return apps;
    252     }
    253 
    254     private void writeSystemApps(Set<String> packageNames, File systemAppsFile) {
    255         try {
    256             FileOutputStream stream = new FileOutputStream(systemAppsFile, false);
    257             XmlSerializer serializer = new FastXmlSerializer();
    258             serializer.setOutput(stream, "utf-8");
    259             serializer.startDocument(null, true);
    260             serializer.startTag(null, TAG_SYSTEM_APPS);
    261             for (String packageName : packageNames) {
    262                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
    263                 serializer.attribute(null, ATTR_VALUE, packageName);
    264                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
    265             }
    266             serializer.endTag(null, TAG_SYSTEM_APPS);
    267             serializer.endDocument();
    268             stream.close();
    269         } catch (IOException e) {
    270             ProvisionLogger.loge("IOException trying to write the system apps", e);
    271         }
    272     }
    273 
    274     private Set<String> readSystemApps(File systemAppsFile) {
    275         Set<String> result = new HashSet<String>();
    276         if (!systemAppsFile.exists()) {
    277             return result;
    278         }
    279         try {
    280             FileInputStream stream = new FileInputStream(systemAppsFile);
    281 
    282             XmlPullParser parser = Xml.newPullParser();
    283             parser.setInput(stream, null);
    284 
    285             int type = parser.next();
    286             int outerDepth = parser.getDepth();
    287             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    288                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    289                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    290                     continue;
    291                 }
    292                 String tag = parser.getName();
    293                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
    294                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
    295                 } else {
    296                     ProvisionLogger.loge("Unknown tag: " + tag);
    297                 }
    298             }
    299             stream.close();
    300         } catch (IOException e) {
    301             ProvisionLogger.loge("IOException trying to read the system apps", e);
    302         } catch (XmlPullParserException e) {
    303             ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
    304         }
    305         return result;
    306     }
    307 
    308     protected Set<String> getRequiredApps() {
    309         HashSet<String> requiredApps = new HashSet<String> (Arrays.asList(
    310                         mContext.getResources().getStringArray(mReqAppsList)));
    311         requiredApps.addAll(Arrays.asList(
    312                         mContext.getResources().getStringArray(mVendorReqAppsList)));
    313         requiredApps.add(mMdmPackageName);
    314         return requiredApps;
    315     }
    316 
    317     /**
    318      * Runs the next task when all packages have been deleted or shuts down the activity if package
    319      * deletion fails.
    320      */
    321     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
    322         private final AtomicInteger mPackageCount = new AtomicInteger(0);
    323 
    324         public PackageDeleteObserver(int packageCount) {
    325             this.mPackageCount.set(packageCount);
    326         }
    327 
    328         @Override
    329         public void packageDeleted(String packageName, int returnCode) {
    330             if (returnCode != PackageManager.DELETE_SUCCEEDED) {
    331                 ProvisionLogger.logw(
    332                         "Could not finish the provisioning: package deletion failed");
    333                 mCallback.onError();
    334             }
    335             int currentPackageCount = mPackageCount.decrementAndGet();
    336             if (currentPackageCount == 0) {
    337                 ProvisionLogger.logi("All non-required system apps have been uninstalled.");
    338                 mCallback.onSuccess();
    339             }
    340         }
    341     }
    342 
    343     public abstract static class Callback {
    344         public abstract void onSuccess();
    345         public abstract void onError();
    346     }
    347 }
    348