blob: 22814405f5f99e0edc51130536175cf54b7743df [file] [log] [blame]
package com.android.clockwork.setup;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_ANDROID;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS;
import static android.provider.Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.provider.Settings;
import android.util.Log;
import com.android.clockwork.common.WearResourceUtil;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Utility class to assist with the selective configuration of packages/components.
* Packages/components can be configured for selective enablement/disablement based on their
* paired-state, using overlay values for config_postSetupPackageConfigList (string array).
*
* Each configuration in the list is of the form:
*
* <package_name>:<config_value>
*
* Where valid config values are:
*
* 0 = disable app for all users
* 1 = disable app for Android users
* 2 = disable app for iOS users
* 3 = disable launchers for all users
* 4 = disable launchers for Android users
* 5 = disable launchers for iOS users
*
* 100 = enable app for all users
* 101 = enable launcher for all users
*
* e.g. -
*
* disable maps for iOS users
* <item>com.google.android.apps.maps:2</item>
*
* disable any launcher apps (those that would appear in the launcher) for dialer for Android
* <item>com.google.android.dialer:1</item>
*
* Note then when enabling/disabling an app, the configuration will be applied to the entire
* package based on the name provided. When enabling/disabling a launcher component, the
* configuration will be applied to any "launcher apps" associated with the provided package (i.e.
* those activities which specify android.intent.action.MAIN and android.intent.category.LAUNCHER in
* the manifest).
*
* By convention, higher value configs are applied first thereby ensuring that ordering is
* deterministic (i.e. by reserving higher order for enabling for example, we can ensure those
* configurations are applied first - so in a potential scenario where a package is disabled for
* only iOS but must subsequently be disabled for only Android, this could be achieved by including
* two configs for the same package - one to enable for all users [100] and one to disable for
* Android users [1], ensuring a known end state for every run).
*
*/
public class PostSetupPackageHelper {
private static final String TAG = "PostSetupPackageHelper";
private static final String PACKAGE_CONFIG_DELIM = ":";
public static final int DISABLE_APP_FOR_ALL = 0;
public static final int DISABLE_APP_FOR_ANDROID = 1;
public static final int DISABLE_APP_FOR_IOS = 2;
public static final int DISABLE_LAUNCHER_FOR_ALL = 3;
public static final int DISABLE_LAUNCHER_FOR_ANDROID = 4;
public static final int DISABLE_LAUNCHER_FOR_IOS = 5;
// higher order config values have precedence (will be applied first)
public static final int ENABLE_APP_FOR_ALL = 100;
public static final int ENABLE_LAUNCHER_FOR_ALL = 101;
// Internal class for Package Configurations, comprising both a package name and configuration
// value, to be applied via package manager (on either a package or component level).
class PackageConfig implements Comparable {
String mPackageName;
int mConfigValue = -1;
PackageConfig(String config) {
String[] parts = config.split(PACKAGE_CONFIG_DELIM);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "New package config: " + config);
}
if (parts.length != 2) {
Log.e(TAG, "Invalid package config: " + config
+ " - expected format <package name>:<config value>");
return;
}
mPackageName = parts[0];
try {
mConfigValue = Integer.valueOf(parts[1]);
} catch (NumberFormatException nfe) {
Log.e(TAG, "Invalid package config: " + config, nfe);
}
}
public boolean isValid() {
return mPackageName != null && mConfigValue >= 0;
}
@Override
public int compareTo(Object o) {
if (this.isValid() && (o instanceof PackageConfig && ((PackageConfig) o).isValid())) {
return ((PackageConfig) o).mConfigValue - this.mConfigValue;
}
return 0;
}
@Override
public String toString() {
return ((mPackageName != null) ? mPackageName : "")
+ PACKAGE_CONFIG_DELIM + mConfigValue;
}
}
private final Context mContext;
private final int mPairedDeviceOSType;
private final String[] mConfigs;
public PostSetupPackageHelper(Context context) {
this(context, WearResourceUtil.getWearableResources(context));
}
@VisibleForTesting
PostSetupPackageHelper(Context context, @Nullable Resources resources) {
mContext = context;
mConfigs = getPackageConfigs(resources);
mPairedDeviceOSType = Settings.Global.getInt(
context.getContentResolver(), PAIRED_DEVICE_OS_TYPE, PAIRED_DEVICE_OS_TYPE_UNKNOWN);
}
/**
* Run the post setup package helper, resulting in the selective
* disabling of any packages specified using the config list.
*/
public void run() {
Arrays.stream(mConfigs).map(PackageConfig::new).sorted().forEach(this::processConfig);
}
public static String[] getPackageConfigs(@Nullable Resources resources) {
if (resources == null) {
return new String[] {};
}
try {
return resources.getStringArray(
com.android.wearable.resources.R.array.config_postSetupPackageConfigList);
} catch (Resources.NotFoundException nfe) {
return new String[] {};
}
}
private void processConfig(PackageConfig packageConfig) {
if (!packageConfig.isValid()) {
Log.w(TAG, "Skipping invalid config: " + packageConfig);
return;
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Applying package config: " + packageConfig);
}
}
switch (packageConfig.mConfigValue) {
case DISABLE_APP_FOR_ALL:
if (mPairedDeviceOSType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
updatePackageIfExists(packageConfig.mPackageName, false);
}
break;
case DISABLE_APP_FOR_ANDROID:
if (mPairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_ANDROID) {
updatePackageIfExists(packageConfig.mPackageName, false);
}
break;
case DISABLE_APP_FOR_IOS:
if (mPairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_IOS) {
updatePackageIfExists(packageConfig.mPackageName, false);
}
break;
case ENABLE_APP_FOR_ALL:
if (mPairedDeviceOSType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
updatePackageIfExists(packageConfig.mPackageName, true);
}
break;
case DISABLE_LAUNCHER_FOR_ALL:
if (mPairedDeviceOSType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
updateLauncherIfExists(packageConfig.mPackageName, false);
}
break;
case DISABLE_LAUNCHER_FOR_ANDROID:
if (mPairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_ANDROID) {
updateLauncherIfExists(packageConfig.mPackageName, false);
}
break;
case DISABLE_LAUNCHER_FOR_IOS:
if (mPairedDeviceOSType == PAIRED_DEVICE_OS_TYPE_IOS) {
updateLauncherIfExists(packageConfig.mPackageName, false);
}
break;
case ENABLE_LAUNCHER_FOR_ALL:
if (mPairedDeviceOSType != PAIRED_DEVICE_OS_TYPE_UNKNOWN) {
updateLauncherIfExists(packageConfig.mPackageName, true);
}
break;
default:
Log.w(TAG, "Skipping invalid package config: " + packageConfig
+ " - unrecognized config value: " + packageConfig.mConfigValue);
break;
}
}
private void updatePackageIfExists(String pkgName, boolean enable) {
PackageManager manager = mContext.getPackageManager();
try {
manager.getPackageInfo(pkgName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to get package info for : " + pkgName, e);
return;
}
Log.i(TAG, (enable ? "Enabling" : "Disabling") + " package: " + pkgName);
manager.setApplicationEnabledSetting(
pkgName,
enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
0);
}
private void updateLauncherIfExists(String pkgName, boolean enable) {
PackageManager manager = mContext.getApplicationContext().getPackageManager();
try {
manager.getPackageInfo(pkgName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to get package info for : " + pkgName, e);
return;
}
// Component name(s) matching launcher activities
List<ComponentName> componentNames = new ArrayList();
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(pkgName);
final int flags = (enable) ? (PackageManager.MATCH_DISABLED_COMPONENTS) : 0;
List<ResolveInfo> resolveList = manager.queryIntentActivities(intent, flags);
ActivityInfo activityInfo = null;
for (ResolveInfo resolveInfo : resolveList) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Checking resolve info: " + resolveInfo.toString());
}
activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "skipping resolve - no activity info");
}
continue;
}
final ComponentName launcherComponent = activityInfo.getComponentName();
if (launcherComponent != null) {
componentNames.add(launcherComponent);
}
}
for (ComponentName componentName: componentNames) {
Log.i(TAG, (enable ? "Enabling" : "Disabling") + " component: "
+ componentName.flattenToShortString());
manager.setComponentEnabledSetting(
componentName,
enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
0);
}
}
}