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 Nfc, 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 profile.
     71     private final boolean mDisableInstallShortcutListeners;
     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 disableInstallShortcutListeners, 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         mDisableInstallShortcutListeners = disableInstallShortcutListeners;
     90     }
     91 
     92     public void run() {
     93         if (mNewProfile) {
     94             disableNfcBluetoothSharing();
     95         }
     96         deleteNonRequiredApps();
     97     }
     98 
     99     private void disableNfcBluetoothSharing() {
    100         ProvisionLogger.logd("Disabling Nfc and Bluetooth sharing.");
    101         disableComponent(new ComponentName("com.android.nfc", "com.android.nfc.BeamShareActivity"));
    102         disableComponent(new ComponentName("com.android.bluetooth",
    103                 "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
    104     }
    105 
    106     private void deleteNonRequiredApps() {
    107         ProvisionLogger.logd("Deleting non required apps.");
    108 
    109         File file = new File(mContext.getFilesDir() + File.separator + "system_apps"
    110                 + File.separator + "user" + mUserId + ".xml");
    111         file.getParentFile().mkdirs(); // Creating the folder if it does not exist
    112 
    113         Set<String> currentApps = getCurrentSystemApps();
    114         Set<String> previousApps;
    115         if (mNewProfile) {
    116             // If this userId was a managed profile before, file may exist. In this case, we ignore
    117             // what is in this file.
    118             previousApps = new HashSet<String>();
    119         } else {
    120             if (file.exists()) {
    121                 previousApps = readSystemApps(file);
    122             } else {
    123                 // If for some reason, the system apps have not been written to file before, we will
    124                 // not delete any system apps this time.
    125                 writeSystemApps(currentApps, file);
    126                 mCallback.onSuccess();
    127                 return;
    128             }
    129         }
    130         writeSystemApps(currentApps, file);
    131         Set<String> newApps = currentApps;
    132         newApps.removeAll(previousApps);
    133 
    134         if (mDisableInstallShortcutListeners) {
    135             Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    136             if (previousApps.isEmpty()) {
    137                 // Here, all the apps are in newApps.
    138                 // It is faster to do it this way than to go through all the apps one by one.
    139                 disableReceivers(actionShortcut);
    140             } else {
    141                 // Here, all the apps are not in newApps. So we have to go through all the new
    142                 // apps one by one.
    143                 for (String newApp : newApps) {
    144                     actionShortcut.setPackage(newApp);
    145                     disableReceivers(actionShortcut);
    146                 }
    147             }
    148         }
    149         Set<String> packagesToDelete = newApps;
    150         packagesToDelete.removeAll(getRequiredApps());
    151         packagesToDelete.retainAll(getCurrentAppsWithLauncher());
    152         // com.android.server.telecom should not handle CALL intents in the managed profile.
    153         if (mNewProfile) {
    154             packagesToDelete.add("com.android.server.telecom");
    155         }
    156         int size = packagesToDelete.size();
    157         if (size > 0) {
    158             PackageDeleteObserver packageDeleteObserver =
    159                         new PackageDeleteObserver(packagesToDelete.size());
    160             for (String packageName : packagesToDelete) {
    161                 try {
    162                     mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
    163                             PackageManager.DELETE_SYSTEM_APP);
    164                 } catch (RemoteException neverThrown) {
    165                     // Never thrown, as we are making local calls.
    166                     ProvisionLogger.loge("This should not happen.", neverThrown);
    167                 }
    168             }
    169         } else {
    170             mCallback.onSuccess();
    171         }
    172     }
    173 
    174     private void disableReceivers(Intent intent) {
    175         List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId);
    176         for (ResolveInfo ri : receivers) {
    177             // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which
    178             // one.
    179             ComponentInfo ci;
    180             if (ri.activityInfo != null) {
    181                 ci = ri.activityInfo;
    182             } else if (ri.serviceInfo != null) {
    183                 ci = ri.serviceInfo;
    184             } else {
    185                 ci = ri.providerInfo;
    186             }
    187             disableComponent(new ComponentName(ci.packageName, ci.name));
    188         }
    189     }
    190 
    191     private void disableComponent(ComponentName toDisable) {
    192         try {
    193             mIpm.setComponentEnabledSetting(toDisable,
    194                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP,
    195                     mUserId);
    196         } catch (RemoteException neverThrown) {
    197             ProvisionLogger.loge("This should not happen.", neverThrown);
    198         } catch (Exception e) {
    199             ProvisionLogger.logw("Component not found, not disabling it: "
    200                 + toDisable.toShortString());
    201         }
    202     }
    203 
    204     /**
    205      * Returns the set of package names of apps that are in the system image,
    206      * whether they have been deleted or not.
    207      */
    208     private Set<String> getCurrentSystemApps() {
    209         Set<String> apps = new HashSet<String>();
    210         List<ApplicationInfo> aInfos = null;
    211         try {
    212             aInfos = mIpm.getInstalledApplications(
    213                     PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
    214         } catch (RemoteException neverThrown) {
    215             // Never thrown, as we are making local calls.
    216             ProvisionLogger.loge("This should not happen.", neverThrown);
    217         }
    218         for (ApplicationInfo aInfo : aInfos) {
    219             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    220                 apps.add(aInfo.packageName);
    221             }
    222         }
    223         return apps;
    224     }
    225 
    226     private Set<String> getCurrentAppsWithLauncher() {
    227         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
    228         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    229         List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
    230                 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId);
    231         Set<String> apps = new HashSet<String>();
    232         for (ResolveInfo resolveInfo : resolveInfos) {
    233             apps.add(resolveInfo.activityInfo.packageName);
    234         }
    235         return apps;
    236     }
    237 
    238     private void writeSystemApps(Set<String> packageNames, File file) {
    239         try {
    240             FileOutputStream stream = new FileOutputStream(file, false);
    241             XmlSerializer serializer = new FastXmlSerializer();
    242             serializer.setOutput(stream, "utf-8");
    243             serializer.startDocument(null, true);
    244             serializer.startTag(null, TAG_SYSTEM_APPS);
    245             for (String packageName : packageNames) {
    246                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
    247                 serializer.attribute(null, ATTR_VALUE, packageName);
    248                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
    249             }
    250             serializer.endTag(null, TAG_SYSTEM_APPS);
    251             serializer.endDocument();
    252             stream.close();
    253         } catch (IOException e) {
    254             ProvisionLogger.loge("IOException trying to write the system apps", e);
    255         }
    256     }
    257 
    258     private Set<String> readSystemApps(File file) {
    259         Set<String> result = new HashSet<String>();
    260         if (!file.exists()) {
    261             return result;
    262         }
    263         try {
    264             FileInputStream stream = new FileInputStream(file);
    265 
    266             XmlPullParser parser = Xml.newPullParser();
    267             parser.setInput(stream, null);
    268 
    269             int type = parser.next();
    270             int outerDepth = parser.getDepth();
    271             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    272                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    273                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    274                     continue;
    275                 }
    276                 String tag = parser.getName();
    277                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
    278                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
    279                 } else {
    280                     ProvisionLogger.loge("Unknown tag: " + tag);
    281                 }
    282             }
    283             stream.close();
    284         } catch (IOException e) {
    285             ProvisionLogger.loge("IOException trying to read the system apps", e);
    286         } catch (XmlPullParserException e) {
    287             ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
    288         }
    289         return result;
    290     }
    291 
    292     protected Set<String> getRequiredApps() {
    293         HashSet<String> requiredApps = new HashSet<String> (Arrays.asList(
    294                         mContext.getResources().getStringArray(mReqAppsList)));
    295         requiredApps.addAll(Arrays.asList(
    296                         mContext.getResources().getStringArray(mVendorReqAppsList)));
    297         requiredApps.add(mMdmPackageName);
    298         return requiredApps;
    299     }
    300 
    301     /**
    302      * Runs the next task when all packages have been deleted or shuts down the activity if package
    303      * deletion fails.
    304      */
    305     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
    306         private final AtomicInteger mPackageCount = new AtomicInteger(0);
    307 
    308         public PackageDeleteObserver(int packageCount) {
    309             this.mPackageCount.set(packageCount);
    310         }
    311 
    312         @Override
    313         public void packageDeleted(String packageName, int returnCode) {
    314             if (returnCode != PackageManager.DELETE_SUCCEEDED) {
    315                 ProvisionLogger.logw(
    316                         "Could not finish the provisioning: package deletion failed");
    317                 mCallback.onError();
    318             }
    319             int currentPackageCount = mPackageCount.decrementAndGet();
    320             if (currentPackageCount == 0) {
    321                 ProvisionLogger.logi("All non-required system apps have been uninstalled.");
    322                 mCallback.onSuccess();
    323             }
    324         }
    325     }
    326 
    327     public abstract static class Callback {
    328         public abstract void onSuccess();
    329         public abstract void onError();
    330     }
    331 }
    332