blob: 2dbef67b553297d080772efcc28326976f6a5c28 [file] [log] [blame]
/*
* 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<String> mRequiredAppsList;
private final List<String> mDisallowedAppsList;
private final List<String> mVendorRequiredAppsList;
private final List<String> 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<String> 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<String> getPackagesToDelete() {
Set<String> 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<String> getNewSystemApps() {
File systemAppsFile = getSystemAppsFile(mContext, mUserId);
systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
Set<String> currentSystemApps = mUtils.getCurrentSystemApps(mIPackageManager, mUserId);
final Set<String> previousSystemApps;
if (mNewProfile) {
// Provisioning case.
previousSystemApps = Collections.<String>emptySet();
} else if (!systemAppsFile.exists()) {
// OTA case.
ProvisionLogger.loge("Could not find the system apps file " +
systemAppsFile.getAbsolutePath());
mCallback.onError();
return Collections.<String>emptySet();
} else {
previousSystemApps = readSystemApps(systemAppsFile);
}
writeSystemApps(currentSystemApps, systemAppsFile);
Set<String> newApps = currentSystemApps;
newApps.removeAll(previousSystemApps);
return newApps;
}
/**
* Remove all packages from the set that are not installed.
*/
private void removeNonInstalledPackages(Set<String> packages) {
Set<String> toBeRemoved = new HashSet<String>();
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<String> getCurrentAppsWithLauncher() {
Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
mUserId);
Set<String> apps = new HashSet<String>();
for (ResolveInfo resolveInfo : resolveInfos) {
apps.add(resolveInfo.activityInfo.packageName);
}
return apps;
}
private Set<String> getSystemInputMethods() {
// InputMethodManager is final so it cannot be mocked.
// So, we're using IInputMethodManager directly because it can be mocked.
List<InputMethodInfo> inputMethods = null;
try {
inputMethods = mIInputMethodManager.getInputMethodList();
} catch (RemoteException e) {
ProvisionLogger.loge("Could not communicate with IInputMethodManager", e);
return Collections.<String>emptySet();
}
Set<String> systemInputMethods = new HashSet<String>();
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<String> 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<String> readSystemApps(File systemAppsFile) {
Set<String> result = new HashSet<String>();
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<String> getRequiredApps() {
HashSet<String> requiredApps = new HashSet<String>();
requiredApps.addAll(mRequiredAppsList);
requiredApps.addAll(mVendorRequiredAppsList);
requiredApps.add(mMdmPackageName);
return requiredApps;
}
private Set<String> getDisallowedApps() {
HashSet<String> disallowedApps = new HashSet<String>();
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();
}
}