/* * Copyright 2014, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.managedprovisioning.task; import android.app.AppGlobals; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Xml; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.view.IInputMethodManager; import com.android.managedprovisioning.ProvisionLogger; import com.android.managedprovisioning.R; import com.android.managedprovisioning.common.Utils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; /** * Deletes all system apps with a launcher that are not in the required set of packages. * Furthermore deletes all disallowed apps. * * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as * required. * * This task may be run when a profile (both for managed device and managed profile) is created. * In that case the newProfile flag should be true. * * It should also be run after a system update with newProfile false, if * {@link #shouldDeleteNonRequiredApps} returns true. Note that only newly installed system apps * will be deleted. */ public class DeleteNonRequiredAppsTask { private final Callback mCallback; private final Context mContext; private final String mMdmPackageName; private final IPackageManager mIPackageManager; private final IInputMethodManager mIInputMethodManager; private final PackageManager mPm; private final List mRequiredAppsList; private final List mDisallowedAppsList; private final List mVendorRequiredAppsList; private final List mVendorDisallowedAppsList; private final int mUserId; private final int mProvisioningType; private final boolean mNewProfile; // If we are provisioning a new managed profile/device. private final boolean mLeaveAllSystemAppsEnabled; private static final String TAG_SYSTEM_APPS = "system-apps"; private static final String TAG_PACKAGE_LIST_ITEM = "item"; private static final String ATTR_VALUE = "value"; public static final int DEVICE_OWNER = 0; public static final int PROFILE_OWNER = 1; public static final int MANAGED_USER = 2; private final Utils mUtils = new Utils(); /** * Provisioning type should be either {@link #DEVICE_OWNER}, {@link #PROFILE_OWNER} or * {@link #MANAGED_USER}. **/ public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int provisioningType, boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback) { this(context, AppGlobals.getPackageManager(), getIInputMethodManager(), mdmPackageName, provisioningType, newProfile, userId, leaveAllSystemAppsEnabled, callback); } @VisibleForTesting DeleteNonRequiredAppsTask(Context context, IPackageManager iPm, IInputMethodManager iimm, String mdmPackageName, int provisioningType, boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback) { mCallback = callback; mContext = context; mMdmPackageName = mdmPackageName; mProvisioningType = provisioningType; mUserId = userId; mNewProfile = newProfile; mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; mPm = context.getPackageManager(); mIPackageManager = iPm; mIInputMethodManager = iimm; int requiredAppsListArray; int vendorRequiredAppsListArray; int disallowedAppsListArray; int vendorDisallowedAppsListArray; if (mProvisioningType == DEVICE_OWNER) { requiredAppsListArray = R.array.required_apps_managed_device; disallowedAppsListArray = R.array.disallowed_apps_managed_device; vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device; vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device; } else if (mProvisioningType == PROFILE_OWNER) { requiredAppsListArray = R.array.required_apps_managed_profile; disallowedAppsListArray = R.array.disallowed_apps_managed_profile; vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_profile; vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_profile; } else if (mProvisioningType == MANAGED_USER) { requiredAppsListArray = R.array.required_apps_managed_user; disallowedAppsListArray = R.array.disallowed_apps_managed_user; vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_user; vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_user; } else { throw new IllegalArgumentException("Provisioning type " + mProvisioningType + " not supported."); } Resources resources = mContext.getResources(); mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray)); mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray)); mVendorRequiredAppsList = Arrays.asList( resources.getStringArray(vendorRequiredAppsListArray)); mVendorDisallowedAppsList = Arrays.asList( resources.getStringArray(vendorDisallowedAppsListArray)); } public void run() { if (mLeaveAllSystemAppsEnabled) { ProvisionLogger.logd("Not deleting non-required apps."); mCallback.onSuccess(); return; } ProvisionLogger.logd("Deleting non required apps."); Set packagesToDelete = getPackagesToDelete(); removeNonInstalledPackages(packagesToDelete); if (packagesToDelete.isEmpty()) { mCallback.onSuccess(); return; } PackageDeleteObserver packageDeleteObserver = new PackageDeleteObserver(packagesToDelete.size()); for (String packageName : packagesToDelete) { ProvisionLogger.logd("Deleting package [" + packageName + "] as user " + mUserId); mPm.deletePackageAsUser(packageName, packageDeleteObserver, PackageManager.DELETE_SYSTEM_APP, mUserId); } } private Set getPackagesToDelete() { Set packagesToDelete = getCurrentAppsWithLauncher(); // Newly installed system apps are uninstalled when they are not required and are either // disallowed or have a launcher icon. packagesToDelete.removeAll(getRequiredApps()); // Don't delete the system input method packages in case of Device owner provisioning. if (mProvisioningType == DEVICE_OWNER || mProvisioningType == MANAGED_USER) { packagesToDelete.removeAll(getSystemInputMethods()); } packagesToDelete.addAll(getDisallowedApps()); // Only consider new system apps. packagesToDelete.retainAll(getNewSystemApps()); return packagesToDelete; } private Set getNewSystemApps() { File systemAppsFile = getSystemAppsFile(mContext, mUserId); systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist Set currentSystemApps = mUtils.getCurrentSystemApps(mIPackageManager, mUserId); final Set previousSystemApps; if (mNewProfile) { // Provisioning case. previousSystemApps = Collections.emptySet(); } else if (!systemAppsFile.exists()) { // OTA case. ProvisionLogger.loge("Could not find the system apps file " + systemAppsFile.getAbsolutePath()); mCallback.onError(); return Collections.emptySet(); } else { previousSystemApps = readSystemApps(systemAppsFile); } writeSystemApps(currentSystemApps, systemAppsFile); Set newApps = currentSystemApps; newApps.removeAll(previousSystemApps); return newApps; } /** * Remove all packages from the set that are not installed. */ private void removeNonInstalledPackages(Set packages) { Set toBeRemoved = new HashSet(); for (String packageName : packages) { try { PackageInfo info = mPm.getPackageInfoAsUser(packageName, 0 /* default flags */, mUserId); if (info == null) { toBeRemoved.add(packageName); } } catch (PackageManager.NameNotFoundException e) { toBeRemoved.add(packageName); } } packages.removeAll(toBeRemoved); } /** * Returns if this task should be run on OTA. * This is indicated by the presence of the system apps file. */ public static boolean shouldDeleteNonRequiredApps(Context context, int userId) { return getSystemAppsFile(context, userId).exists(); } static File getSystemAppsFile(Context context, int userId) { return new File(context.getFilesDir() + File.separator + "system_apps" + File.separator + "user" + userId + ".xml"); } private Set getCurrentAppsWithLauncher() { Intent launcherIntent = new Intent(Intent.ACTION_MAIN); launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); List resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent, PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, mUserId); Set apps = new HashSet(); for (ResolveInfo resolveInfo : resolveInfos) { apps.add(resolveInfo.activityInfo.packageName); } return apps; } private Set getSystemInputMethods() { // InputMethodManager is final so it cannot be mocked. // So, we're using IInputMethodManager directly because it can be mocked. List inputMethods = null; try { inputMethods = mIInputMethodManager.getInputMethodList(); } catch (RemoteException e) { ProvisionLogger.loge("Could not communicate with IInputMethodManager", e); return Collections.emptySet(); } Set systemInputMethods = new HashSet(); for (InputMethodInfo inputMethodInfo : inputMethods) { ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo; if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { systemInputMethods.add(inputMethodInfo.getPackageName()); } } return systemInputMethods; } private void writeSystemApps(Set packageNames, File systemAppsFile) { try { FileOutputStream stream = new FileOutputStream(systemAppsFile, false); XmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(stream, "utf-8"); serializer.startDocument(null, true); serializer.startTag(null, TAG_SYSTEM_APPS); for (String packageName : packageNames) { serializer.startTag(null, TAG_PACKAGE_LIST_ITEM); serializer.attribute(null, ATTR_VALUE, packageName); serializer.endTag(null, TAG_PACKAGE_LIST_ITEM); } serializer.endTag(null, TAG_SYSTEM_APPS); serializer.endDocument(); stream.close(); } catch (IOException e) { ProvisionLogger.loge("IOException trying to write the system apps", e); } } private Set readSystemApps(File systemAppsFile) { Set result = new HashSet(); if (!systemAppsFile.exists()) { return result; } try { FileInputStream stream = new FileInputStream(systemAppsFile); XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); int type = parser.next(); int outerDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tag = parser.getName(); if (tag.equals(TAG_PACKAGE_LIST_ITEM)) { result.add(parser.getAttributeValue(null, ATTR_VALUE)); } else { ProvisionLogger.loge("Unknown tag: " + tag); } } stream.close(); } catch (IOException e) { ProvisionLogger.loge("IOException trying to read the system apps", e); } catch (XmlPullParserException e) { ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e); } return result; } protected Set getRequiredApps() { HashSet requiredApps = new HashSet(); requiredApps.addAll(mRequiredAppsList); requiredApps.addAll(mVendorRequiredAppsList); requiredApps.add(mMdmPackageName); return requiredApps; } private Set getDisallowedApps() { HashSet disallowedApps = new HashSet(); disallowedApps.addAll(mDisallowedAppsList); disallowedApps.addAll(mVendorDisallowedAppsList); return disallowedApps; } /** * Runs the next task when all packages have been deleted or shuts down the activity if package * deletion fails. */ class PackageDeleteObserver extends IPackageDeleteObserver.Stub { private final AtomicInteger mPackageCount = new AtomicInteger(0); public PackageDeleteObserver(int packageCount) { this.mPackageCount.set(packageCount); } @Override public void packageDeleted(String packageName, int returnCode) { if (returnCode != PackageManager.DELETE_SUCCEEDED) { ProvisionLogger.logw( "Could not finish the provisioning: package deletion failed"); mCallback.onError(); return; } int currentPackageCount = mPackageCount.decrementAndGet(); if (currentPackageCount == 0) { ProvisionLogger.logi("All non-required system apps with launcher icon, " + "and all disallowed apps have been uninstalled."); mCallback.onSuccess(); } } } private static IInputMethodManager getIInputMethodManager() { IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); return IInputMethodManager.Stub.asInterface(b); } public abstract static class Callback { public abstract void onSuccess(); public abstract void onError(); } }