summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorjackqdyulei <jackqdyulei@google.com>2017-03-15 15:13:10 -0700
committerjackqdyulei <jackqdyulei@google.com>2017-03-28 17:05:19 -0700
commit82d07983b46b4c6a1b7a3c431c57ff45e84e72be (patch)
tree164844518b8c14a17e11c18bb06d984869af6117 /src/com
parentddba96670048a07b98e5c1e0d9f3d9bd4ebe9b38 (diff)
downloadpackages_apps_Settings-82d07983b46b4c6a1b7a3c431c57ff45e84e72be.tar.gz
packages_apps_Settings-82d07983b46b4c6a1b7a3c431c57ff45e84e72be.tar.bz2
packages_apps_Settings-82d07983b46b4c6a1b7a3c431c57ff45e84e72be.zip
Create Controller for app buttons
These two buttons(uninstall + forcestop) are used in both battery page and app page, we should move the logic for these two buttons into one place. This cl creates the AppButtonsPreferenceController for the above purpose. This cl only copies the logic to controller but hasn't make InstalledAppDetails use this controller. Since DialogFragment could not use function in controller directly, the controller expose DialogListener and all the fragments must implement this interface. Then they can delegate the method call to controller directly. The following cl will: 1. Make the InstalledAppDetails be compatible to controller 2. Make the InstalledAppDetails use this controller. Bug: 35810915 Test: RunSettingsRoboTests Change-Id: Ie2aa8064bcec3003233896c18be772825b12930a
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java21
-rw-r--r--src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java15
-rw-r--r--src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java62
-rw-r--r--src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java640
-rw-r--r--src/com/android/settings/fuelgauge/ButtonActionDialogFragment.java104
5 files changed, 825 insertions, 17 deletions
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
index e988fdadf1..a154a2fb93 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
@@ -129,4 +129,25 @@ public interface DevicePolicyManagerWrapper {
* @see android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts
*/
List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user);
+
+ /**
+ * Calls {@code DevicePolicyManager.isDeviceOwnerAppOnAnyUser()}.
+ *
+ * @see android.app.admin.DevicePolicyManager#isDeviceOwnerAppOnAnyUser
+ */
+ boolean isDeviceOwnerAppOnAnyUser(String packageName);
+
+ /**
+ * Calls {@code DevicePolicyManager.packageHasActiveAdmins()}.
+ *
+ * @see android.app.admin.DevicePolicyManager#packageHasActiveAdmins
+ */
+ boolean packageHasActiveAdmins(String packageName);
+
+ /**
+ * Calls {@code DevicePolicyManager.isUninstallInQueue()}.
+ *
+ * @see android.app.admin.DevicePolicyManager#isUninstallInQueue
+ */
+ boolean isUninstallInQueue(String packageName);
}
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
index 18563b587f..95a154bff5 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
@@ -101,4 +101,19 @@ public class DevicePolicyManagerWrapperImpl implements DevicePolicyManagerWrappe
public List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user) {
return mDpm.getOwnerInstalledCaCerts(user);
}
+
+ @Override
+ public boolean isDeviceOwnerAppOnAnyUser(String packageName) {
+ return mDpm.isDeviceOwnerAppOnAnyUser(packageName);
+ }
+
+ @Override
+ public boolean packageHasActiveAdmins(String packageName) {
+ return mDpm.packageHasActiveAdmins(packageName);
+ }
+
+ @Override
+ public boolean isUninstallInQueue(String packageName) {
+ return mDpm.isUninstallInQueue(packageName);
+ }
}
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index ece6b78930..4a8aa9c840 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -16,11 +16,19 @@
package com.android.settings.fuelgauge;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
@@ -36,6 +44,8 @@ import com.android.settings.Utils;
import com.android.settings.applications.AppHeaderController;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceController;
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
+import com.android.settings.enterprise.DevicePolicyManagerWrapperImpl;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.ApplicationsState;
@@ -50,7 +60,8 @@ import java.util.List;
*
* This fragment will replace {@link PowerUsageDetail}
*/
-public class AdvancedPowerUsageDetail extends PowerUsageBase {
+public class AdvancedPowerUsageDetail extends PowerUsageBase implements
+ ButtonActionDialogFragment.AppButtonsDialogListener {
public static final String TAG = "AdvancedPowerUsageDetail";
public static final String EXTRA_UID = "extra_uid";
@@ -67,6 +78,9 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
private static final String KEY_PREF_POWER_USAGE = "app_power_usage";
private static final String KEY_PREF_HEADER = "header_view";
+ private static final int REQUEST_UNINSTALL = 0;
+ private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
+
@VisibleForTesting
LayoutPreference mHeaderPreference;
@VisibleForTesting
@@ -77,6 +91,11 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
private Preference mForegroundPreference;
private Preference mBackgroundPreference;
private Preference mPowerUsagePreference;
+ private AppButtonsPreferenceController mAppButtonsPreferenceController;
+
+ private DevicePolicyManagerWrapper mDpm;
+ private UserManager mUserManager;
+ private PackageManager mPackageManager;
public static void startBatteryDetailPage(SettingsActivity caller, PreferenceFragment fragment,
BatteryStatsHelper helper, int which, BatteryEntry entry, String usagePercent) {
@@ -113,6 +132,17 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
}
@Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ mState = ApplicationsState.getInstance(getActivity().getApplication());
+ mDpm = new DevicePolicyManagerWrapperImpl(
+ (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
+ mPackageManager = activity.getPackageManager();
+ }
+
+ @Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -120,7 +150,6 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND);
mPowerUsagePreference = findPreference(KEY_PREF_POWER_USAGE);
mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER);
- mState = ApplicationsState.getInstance(getActivity().getApplication());
final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
if (packageName != null) {
@@ -160,7 +189,13 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
if (mAppEntry == null) {
controller.setLabel(bundle.getString(EXTRA_LABEL));
- controller.setIcon(getContext().getDrawable(bundle.getInt(EXTRA_ICON_ID)));
+
+ final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
+ if (iconId == 0) {
+ controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
+ } else {
+ controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
+ }
} else {
mState.ensureIcon(mAppEntry);
controller.setLabel(mAppEntry);
@@ -196,9 +231,26 @@ public class AdvancedPowerUsageDetail extends PowerUsageBase {
controllers.add(new BackgroundActivityPreferenceController(context, uid));
controllers.add(new BatteryOptimizationPreferenceController(
(SettingsActivity) getActivity(), this));
- controllers.add(
- new AppButtonsPreferenceController(getActivity(), getLifecycle(), packageName));
+ mAppButtonsPreferenceController = new AppButtonsPreferenceController(
+ (SettingsActivity) getActivity(), this, getLifecycle(), packageName, mState, mDpm,
+ mUserManager, mPackageManager, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
+ controllers.add(mAppButtonsPreferenceController);
return controllers;
}
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (mAppButtonsPreferenceController != null) {
+ mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @Override
+ public void handleDialogClick(int id) {
+ if (mAppButtonsPreferenceController != null) {
+ mAppButtonsPreferenceController.handleDialogClick(id);
+ }
+ }
}
diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
index b02c8c5edb..f7cb19103d 100644
--- a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
@@ -17,42 +17,139 @@
package com.android.settings.fuelgauge;
import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
import android.view.View;
+import android.webkit.IWebViewUpdateService;
import android.widget.Button;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.DeviceAdminAdd;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceController;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.core.lifecycle.Lifecycle;
import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnDestroy;
+import com.android.settings.core.lifecycle.events.OnPause;
import com.android.settings.core.lifecycle.events.OnResume;
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.applications.ApplicationsState;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
/**
- * Controller to control the uninstall button and forcestop button
+ * Controller to control the uninstall button and forcestop button. All fragments that use
+ * this controller should implement {@link ButtonActionDialogFragment.AppButtonsDialogListener} and
+ * handle {@link Fragment#onActivityResult(int, int, Intent)}
+ *
+ * An easy way to handle them is to delegate them to {@link #handleDialogClick(int)} and
+ * {@link #handleActivityResult(int, int, Intent)} in this controller.
*/
-//TODO(b/35810915): refine the button logic and make InstalledAppDetails use this controller
-//TODO(b/35810915): add test for this file
+//TODO(b/35810915): Make InstalledAppDetails use this controller
public class AppButtonsPreferenceController extends PreferenceController implements
- LifecycleObserver, OnResume {
+ LifecycleObserver, OnResume, OnPause, OnDestroy, View.OnClickListener,
+ ApplicationsState.Callbacks {
+ public static final String APP_CHG = "chg";
+
+ private static final String TAG = "AppButtonsPrefCtl";
private static final String KEY_ACTION_BUTTONS = "action_buttons";
+ private static final boolean LOCAL_LOGV = false;
+
+ @VisibleForTesting
+ final HashSet<String> mHomePackages = new HashSet<>();
+ @VisibleForTesting
+ ApplicationsState mState;
+ @VisibleForTesting
+ ApplicationsState.AppEntry mAppEntry;
+ @VisibleForTesting
+ PackageInfo mPackageInfo;
+ @VisibleForTesting
+ Button mForceStopButton;
+ @VisibleForTesting
+ Button mUninstallButton;
+ @VisibleForTesting
+ boolean mDisableAfterUninstall = false;
+
+ private final int mRequestUninstall;
+ private final int mRequestRemoveDeviceAdmin;
+
+ private ApplicationsState.Session mSession;
+ private DevicePolicyManagerWrapper mDpm;
+ private UserManager mUserManager;
+ private PackageManager mPm;
+ private SettingsActivity mActivity;
+ private Fragment mFragment;
+ private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
+ private MetricsFeatureProvider mMetricsFeatureProvider;
- private ApplicationsState.AppEntry mAppEntry;
private LayoutPreference mButtonsPref;
- private Button mForceStopButton;
- private Button mUninstallButton;
+ private String mPackageName;
+ private int mUserId;
+ private boolean mUpdatedSysApp = false;
+ private boolean mListeningToPackageRemove = false;
+ private boolean mFinishing = false;
+ private boolean mAppsControlDisallowedBySystem;
- public AppButtonsPreferenceController(Activity activity, Lifecycle lifecycle,
- String packageName) {
+ public AppButtonsPreferenceController(SettingsActivity activity, Fragment fragment,
+ Lifecycle lifecycle, String packageName, ApplicationsState state,
+ DevicePolicyManagerWrapper dpm, UserManager userManager,
+ PackageManager packageManager, int requestUninstall, int requestRemoveDeviceAdmin) {
super(activity);
+ if (!(fragment instanceof ButtonActionDialogFragment.AppButtonsDialogListener)) {
+ throw new IllegalArgumentException(
+ "Fragment should implement AppButtonsDialogListener");
+ }
+
+ mMetricsFeatureProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
+
+ mState = state;
+ mSession = mState.newSession(this);
+ mDpm = dpm;
+ mUserManager = userManager;
+ mPm = packageManager;
+ mPackageName = packageName;
+ mActivity = activity;
+ mFragment = fragment;
+ mUserId = UserHandle.myUserId();
+ mRequestUninstall = requestUninstall;
+ mRequestRemoveDeviceAdmin = requestRemoveDeviceAdmin;
+
lifecycle.addObserver(this);
- ApplicationsState state = ApplicationsState.getInstance(activity.getApplication());
if (packageName != null) {
- mAppEntry = state.getEntry(packageName, UserHandle.myUserId());
+ mAppEntry = mState.getEntry(packageName, mUserId);
+ } else {
+ mFinishing = true;
}
}
@@ -72,6 +169,7 @@ public class AppButtonsPreferenceController extends PreferenceController impleme
mForceStopButton = (Button) mButtonsPref.findViewById(R.id.right_button);
mForceStopButton.setText(R.string.force_stop);
+ mForceStopButton.setEnabled(false);
}
}
@@ -82,6 +180,524 @@ public class AppButtonsPreferenceController extends PreferenceController impleme
@Override
public void onResume() {
- //TODO(b/35810915): check and update the status of buttons
+ mSession.resume();
+ if (isAvailable() && !mFinishing) {
+ mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(mActivity,
+ UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(mActivity,
+ UserManager.DISALLOW_APPS_CONTROL, mUserId);
+
+ if (!refreshUi()) {
+ setIntentAndFinish(true);
+ }
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mSession.pause();
+ }
+
+ @Override
+ public void onDestroy() {
+ stopListeningToPackageRemove();
+ mSession.release();
+ }
+
+ @Override
+ public void onClick(View v) {
+ final String packageName = mAppEntry.info.packageName;
+ final int id = v.getId();
+ if (id == R.id.left_button) {
+ // Uninstall
+ if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
+ stopListeningToPackageRemove();
+ Intent uninstallDaIntent = new Intent(mActivity, DeviceAdminAdd.class);
+ uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
+ packageName);
+ mMetricsFeatureProvider.action(mActivity,
+ MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN);
+ mFragment.startActivityForResult(uninstallDaIntent, mRequestRemoveDeviceAdmin);
+ return;
+ }
+ RestrictedLockUtils.EnforcedAdmin admin =
+ RestrictedLockUtils.checkIfUninstallBlocked(mActivity,
+ packageName, mUserId);
+ boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
+ RestrictedLockUtils.hasBaseUserRestriction(mActivity, packageName, mUserId);
+ if (admin != null && !uninstallBlockedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin);
+ } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
+ // If the system app has an update and this is the only user on the device,
+ // then offer to downgrade the app, otherwise only offer to disable the
+ // app for this user.
+ if (mUpdatedSysApp && isSingleUser()) {
+ showDialogInner(ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE);
+ } else {
+ showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE);
+ }
+ } else {
+ mMetricsFeatureProvider.action(
+ mActivity,
+ mAppEntry.info.enabled
+ ? MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP
+ : MetricsProto.MetricsEvent.ACTION_SETTINGS_ENABLE_APP);
+ AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT));
+ }
+ } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ uninstallPkg(packageName, true, false);
+ } else {
+ uninstallPkg(packageName, false, false);
+ }
+ } else if (id == R.id.right_button) {
+ // force stop
+ if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ mActivity, mAppsControlDisallowedAdmin);
+ } else {
+ showDialogInner(ButtonActionDialogFragment.DialogType.FORCE_STOP);
+ }
+ }
+ }
+
+ public void handleActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == mRequestUninstall) {
+ if (mDisableAfterUninstall) {
+ mDisableAfterUninstall = false;
+ AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
+ }
+ refreshAndFinishIfPossible();
+ } else if (requestCode == mRequestRemoveDeviceAdmin) {
+ refreshAndFinishIfPossible();
+ }
+ }
+
+ public void handleDialogClick(int id) {
+ switch (id) {
+ case ButtonActionDialogFragment.DialogType.DISABLE:
+ mMetricsFeatureProvider.action(mActivity,
+ MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
+ AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
+ break;
+ case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE:
+ mMetricsFeatureProvider.action(mActivity,
+ MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
+ uninstallPkg(mAppEntry.info.packageName, false, true);
+ break;
+ case ButtonActionDialogFragment.DialogType.FORCE_STOP:
+ forceStopPackage(mAppEntry.info.packageName);
+ break;
+ }
+ }
+
+ @Override
+ public void onRunningStateChanged(boolean running) {
+
+ }
+
+ @Override
+ public void onPackageListChanged() {
+ refreshUi();
+ }
+
+ @Override
+ public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+
+ }
+
+ @Override
+ public void onPackageIconChanged() {
+
+ }
+
+ @Override
+ public void onPackageSizeChanged(String packageName) {
+
}
+
+ @Override
+ public void onAllSizesComputed() {
+
+ }
+
+ @Override
+ public void onLauncherInfoChanged() {
+
+ }
+
+ @Override
+ public void onLoadEntriesCompleted() {
+
+ }
+
+ @VisibleForTesting
+ void retrieveAppEntry() {
+ mAppEntry = mState.getEntry(mPackageName, mUserId);
+ if (mAppEntry != null) {
+ try {
+ mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS |
+ PackageManager.MATCH_ANY_USER |
+ PackageManager.GET_SIGNATURES |
+ PackageManager.GET_PERMISSIONS);
+
+ mPackageName = mAppEntry.info.packageName;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
+ mPackageInfo = null;
+ }
+ } else {
+ mPackageInfo = null;
+ }
+ }
+
+ @VisibleForTesting
+ void updateUninstallButton() {
+ final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ boolean enabled = true;
+ if (isBundled) {
+ enabled = handleDisableable(mUninstallButton);
+ } else {
+ if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
+ && mUserManager.getUsers().size() >= 2) {
+ // When we have multiple users, there is a separate menu
+ // to uninstall for all users.
+ enabled = false;
+ }
+ }
+ // If this is a device admin, it can't be uninstalled or disabled.
+ // We do this here so the text of the button is still set correctly.
+ if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
+ enabled = false;
+ }
+
+ // We don't allow uninstalling DO/PO on *any* users, because if it's a system app,
+ // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
+ // will clear data on all users.
+ if (isProfileOrDeviceOwner(mPackageInfo.packageName)) {
+ enabled = false;
+ }
+
+ // Don't allow uninstalling the device provisioning package.
+ if (Utils.isDeviceProvisioningPackage(mContext.getResources(),
+ mAppEntry.info.packageName)) {
+ enabled = false;
+ }
+
+ // If the uninstall intent is already queued, disable the uninstall button
+ if (mDpm.isUninstallInQueue(mPackageName)) {
+ enabled = false;
+ }
+
+ // Home apps need special handling. Bundled ones we don't risk downgrading
+ // because that can interfere with home-key resolution. Furthermore, we
+ // can't allow uninstallation of the only home app, and we don't want to
+ // allow uninstallation of an explicitly preferred one -- the user can go
+ // to Home settings and pick a different one, after which we'll permit
+ // uninstallation of the now-not-default one.
+ if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
+ if (isBundled) {
+ enabled = false;
+ } else {
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+ if (currentDefaultHome == null) {
+ // No preferred default, so permit uninstall only when
+ // there is more than one candidate
+ enabled = (mHomePackages.size() > 1);
+ } else {
+ // There is an explicit default home app -- forbid uninstall of
+ // that one, but permit it for installed-but-inactive ones.
+ enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ }
+ }
+ }
+
+ if (mAppsControlDisallowedBySystem) {
+ enabled = false;
+ }
+
+ if (isFallbackPackage(mAppEntry.info.packageName)) {
+ enabled = false;
+ }
+
+ mUninstallButton.setEnabled(enabled);
+ if (enabled) {
+ // Register listener
+ mUninstallButton.setOnClickListener(this);
+ }
+ }
+
+ /**
+ * Finish this fragment and return data if possible
+ */
+ private void setIntentAndFinish(boolean appChanged) {
+ if (LOCAL_LOGV) {
+ Log.i(TAG, "appChanged=" + appChanged);
+ }
+ Intent intent = new Intent();
+ intent.putExtra(APP_CHG, appChanged);
+ mActivity.finishPreferencePanel(mFragment, Activity.RESULT_OK, intent);
+ mFinishing = true;
+ }
+
+ private void refreshAndFinishIfPossible() {
+ if (!refreshUi()) {
+ setIntentAndFinish(true);
+ } else {
+ startListeningToPackageRemove();
+ }
+ }
+
+ @VisibleForTesting
+ boolean isFallbackPackage(String packageName) {
+ try {
+ IWebViewUpdateService webviewUpdateService =
+ IWebViewUpdateService.Stub.asInterface(
+ ServiceManager.getService("webviewupdate"));
+ if (webviewUpdateService.isFallbackPackage(packageName)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ void updateForceStopButton() {
+ if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
+ // User can't force stop device admin.
+ Log.w(TAG, "User can't force stop device admin");
+ updateForceStopButtonInner(false);
+ } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
+ // If the app isn't explicitly stopped, then always show the
+ // force stop button.
+ Log.w(TAG, "App is not explicitly stopped");
+ updateForceStopButtonInner(true);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+ Uri.fromParts("package", mAppEntry.info.packageName, null));
+ intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mAppEntry.info.packageName});
+ intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
+ Log.d(TAG, "Sending broadcast to query restart status for "
+ + mAppEntry.info.packageName);
+ mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
+ }
+ }
+
+ @VisibleForTesting
+ void updateForceStopButtonInner(boolean enabled) {
+ if (mAppsControlDisallowedBySystem) {
+ mForceStopButton.setEnabled(false);
+ } else {
+ mForceStopButton.setEnabled(enabled);
+ mForceStopButton.setOnClickListener(this);
+ }
+ }
+
+ @VisibleForTesting
+ void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
+ stopListeningToPackageRemove();
+ // Create new intent to launch Uninstaller activity
+ Uri packageUri = Uri.parse("package:" + packageName);
+ Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
+ uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
+
+ mMetricsFeatureProvider.action(
+ mActivity, MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
+ mFragment.startActivityForResult(uninstallIntent, mRequestUninstall);
+ mDisableAfterUninstall = andDisable;
+ }
+
+ @VisibleForTesting
+ void forceStopPackage(String pkgName) {
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext,
+ MetricsProto.MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
+ ActivityManager am = (ActivityManager) mActivity.getSystemService(
+ Context.ACTIVITY_SERVICE);
+ Log.d(TAG, "Stopping package " + pkgName);
+ am.forceStopPackage(pkgName);
+ int userId = UserHandle.getUserId(mAppEntry.info.uid);
+ mState.invalidatePackage(pkgName, userId);
+ ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId);
+ if (newEnt != null) {
+ mAppEntry = newEnt;
+ }
+ updateForceStopButton();
+ }
+
+ @VisibleForTesting
+ boolean handleDisableable(Button button) {
+ boolean disableable = false;
+ // Try to prevent the user from bricking their phone
+ // by not allowing disabling of apps signed with the
+ // system cert and any launcher app in the system.
+ if (mHomePackages.contains(mAppEntry.info.packageName)
+ || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) {
+ // Disable button for core system applications.
+ button.setText(R.string.disable_text);
+ } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
+ button.setText(R.string.disable_text);
+ disableable = true;
+ } else {
+ button.setText(R.string.enable_text);
+ disableable = true;
+ }
+
+ return disableable;
+ }
+
+ @VisibleForTesting
+ boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) {
+ return Utils.isSystemPackage(resources, pm, packageInfo);
+ }
+
+ private boolean isDisabledUntilUsed() {
+ return mAppEntry.info.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+ }
+
+ private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) {
+ ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id);
+ newFragment.setTargetFragment(mFragment, 0);
+ newFragment.show(mActivity.getFragmentManager(), "dialog " + id);
+ }
+
+ /** Returns whether there is only one user on this device, not including the system-only user */
+ private boolean isSingleUser() {
+ final int userCount = mUserManager.getUserCount();
+ return userCount == 1
+ || (mUserManager.isSplitSystemUser() && userCount == 2);
+ }
+
+ /** Returns if the supplied package is device owner or profile owner of at least one user */
+ private boolean isProfileOrDeviceOwner(String packageName) {
+ List<UserInfo> userInfos = mUserManager.getUsers();
+ if (mDpm.isDeviceOwnerAppOnAnyUser(packageName)) {
+ return true;
+ }
+ for (int i = 0, size = userInfos.size(); i < size; i++) {
+ ComponentName cn = mDpm.getProfileOwnerAsUser(userInfos.get(i).id);
+ if (cn != null && cn.getPackageName().equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
+ Log.d(TAG, "Got broadcast response: Restart status for "
+ + mAppEntry.info.packageName + " " + enabled);
+ updateForceStopButtonInner(enabled);
+ }
+ };
+
+ private boolean signaturesMatch(String pkg1, String pkg2) {
+ if (pkg1 != null && pkg2 != null) {
+ try {
+ final int match = mPm.checkSignatures(pkg1, pkg2);
+ if (match >= PackageManager.SIGNATURE_MATCH) {
+ return true;
+ }
+ } catch (Exception e) {
+ // e.g. named alternate package not found during lookup;
+ // this is an expected case sometimes
+ }
+ }
+ return false;
+ }
+
+ private boolean refreshUi() {
+ retrieveAppEntry();
+ if (mAppEntry == null || mPackageInfo == null) {
+ return false;
+ }
+ // Get list of "home" apps and trace through any meta-data references
+ List<ResolveInfo> homeActivities = new ArrayList<>();
+ mPm.getHomeActivities(homeActivities);
+ mHomePackages.clear();
+ for (int i = 0, size = homeActivities.size(); i < size; i++) {
+ ResolveInfo ri = homeActivities.get(i);
+ final String activityPkg = ri.activityInfo.packageName;
+ mHomePackages.add(activityPkg);
+
+ // Also make sure to include anything proxying for the home app
+ final Bundle metadata = ri.activityInfo.metaData;
+ if (metadata != null) {
+ final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
+ if (signaturesMatch(metaPkg, activityPkg)) {
+ mHomePackages.add(metaPkg);
+ }
+ }
+ }
+
+ updateUninstallButton();
+ updateForceStopButton();
+
+ return true;
+ }
+
+ private void startListeningToPackageRemove() {
+ if (mListeningToPackageRemove) {
+ return;
+ }
+ mListeningToPackageRemove = true;
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mActivity.registerReceiver(mPackageRemovedReceiver, filter);
+ }
+
+ private void stopListeningToPackageRemove() {
+ if (!mListeningToPackageRemove) {
+ return;
+ }
+ mListeningToPackageRemove = false;
+ mActivity.unregisterReceiver(mPackageRemovedReceiver);
+ }
+
+
+ /**
+ * Changes the status of disable/enable for a package
+ */
+ private class DisableChangerRunnable implements Runnable {
+ final PackageManager mPm;
+ final String mPackageName;
+ final int mState;
+
+ public DisableChangerRunnable(PackageManager pm, String packageName, int state) {
+ mPm = pm;
+ mPackageName = packageName;
+ mState = state;
+ }
+
+ @Override
+ public void run() {
+ mPm.setApplicationEnabledSetting(mPackageName, mState, 0);
+ }
+ }
+
+ /**
+ * Receiver to listen to the remove action for packages
+ */
+ private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String packageName = intent.getData().getSchemeSpecificPart();
+ if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) {
+ mActivity.finishAndRemoveTask();
+ }
+ }
+ };
+
}
diff --git a/src/com/android/settings/fuelgauge/ButtonActionDialogFragment.java b/src/com/android/settings/fuelgauge/ButtonActionDialogFragment.java
new file mode 100644
index 0000000000..b17cd54167
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/ButtonActionDialogFragment.java
@@ -0,0 +1,104 @@
+package com.android.settings.fuelgauge;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Fragment to show the dialog for uninstall or forcestop. This fragment uses function in
+ * target fragment to handle the dialog button click.
+ */
+public class ButtonActionDialogFragment extends InstrumentedDialogFragment implements
+ DialogInterface.OnClickListener {
+
+ /**
+ * Interface to handle the dialog click
+ */
+ interface AppButtonsDialogListener {
+ void handleDialogClick(int type);
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DialogType.DISABLE,
+ DialogType.SPECIAL_DISABLE,
+ DialogType.FORCE_STOP
+ })
+ public @interface DialogType {
+ int DISABLE = 0;
+ int SPECIAL_DISABLE = 1;
+ int FORCE_STOP = 2;
+ }
+
+ private static final String ARG_ID = "id";
+ @VisibleForTesting
+ int mId;
+
+ public static ButtonActionDialogFragment newInstance(@DialogType int id) {
+ ButtonActionDialogFragment dialogFragment = new ButtonActionDialogFragment();
+ Bundle args = new Bundle(1);
+ args.putInt(ARG_ID, id);
+ dialogFragment.setArguments(args);
+
+ return dialogFragment;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ //TODO(35810915): update the metrics label because for now this fragment will be shown
+ // in two screens
+ return MetricsProto.MetricsEvent.DIALOG_APP_INFO_ACTION;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle bundle = getArguments();
+ mId = bundle.getInt(ARG_ID);
+ Dialog dialog = createDialog(mId);
+ if (dialog == null) {
+ throw new IllegalArgumentException("unknown id " + mId);
+ }
+ return dialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final AppButtonsDialogListener lsn =
+ (AppButtonsDialogListener) getTargetFragment();
+ lsn.handleDialogClick(mId);
+ }
+
+ private AlertDialog createDialog(int id) {
+ final Context context = getContext();
+ switch (id) {
+ case DialogType.DISABLE:
+ case DialogType.SPECIAL_DISABLE:
+ return new AlertDialog.Builder(context)
+ .setMessage(R.string.app_disable_dlg_text)
+ .setPositiveButton(R.string.app_disable_dlg_positive, this)
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ case DialogType.FORCE_STOP:
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.force_stop_dlg_title)
+ .setMessage(R.string.force_stop_dlg_text)
+ .setPositiveButton(R.string.dlg_ok, this)
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ }
+ return null;
+ }
+}
+