diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ccb8d1b08d..0418d51dfc0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3312,13 +3312,13 @@
-
+ android:name=".slices.SliceBroadcastReceiver" >
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 45aadf38d12..686cb724122 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6728,14 +6728,14 @@
No sound
-
- No sound (Total Silence)
+
+ Total Silence
No sound except %1$s
-
- No sound except alarms, media and system feedback (Alarms only)
+
+ No sound except alarms and media
Turn on automatically
@@ -7315,8 +7315,11 @@
Alarms
-
- Media and system feedback
+
+ Media
+
+
+ Includes system feedback
Reminders
@@ -7336,6 +7339,9 @@
If the same person calls a second time within a %d minute period
+
+ Custom
+
Automatically turn on
diff --git a/res/xml/zen_mode_behavior_settings.xml b/res/xml/zen_mode_behavior_settings.xml
index c0849daaf48..6aeebe69a9b 100644
--- a/res/xml/zen_mode_behavior_settings.xml
+++ b/res/xml/zen_mode_behavior_settings.xml
@@ -34,7 +34,8 @@
+ android:title="@string/zen_mode_media_system_other"
+ android:summary="@string/zen_mode_media_system_other_secondary_text"/>
installedAdmin = findAdminWithPackageName(packageName);
+ if (!installedAdmin.isPresent()) {
Log.w(TAG, "No component specified in " + action);
finish();
return;
}
+ who = installedAdmin.get();
+ mUninstalling = true;
}
if (action != null && action.equals(DevicePolicyManager.ACTION_SET_PROFILE_OWNER)) {
@@ -692,6 +689,18 @@ public class DeviceAdminAdd extends Activity {
return info != null ? info.isManagedProfile() : false;
}
+ /**
+ * @return an {@link Optional} containing the admin with a given package name, if it exists,
+ * or {@link Optional#empty()} otherwise.
+ */
+ private Optional findAdminWithPackageName(String packageName) {
+ List admins = mDPM.getActiveAdmins();
+ if (admins == null) {
+ return Optional.empty();
+ }
+ return admins.stream().filter(i -> i.getPackageName().equals(packageName)).findAny();
+ }
+
private boolean isAdminUninstallable() {
// System apps can't be uninstalled.
return !mDeviceAdmin.getActivityInfo().applicationInfo.isSystemApp();
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 2098bd6bf48..f78459f6980 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -75,6 +75,7 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.PictureInPictureDetails;
diff --git a/src/com/android/settings/applications/InstalledAppDetailsTop.java b/src/com/android/settings/applications/InstalledAppDetailsTop.java
index 174a86a2dfc..8090de03ea2 100644
--- a/src/com/android/settings/applications/InstalledAppDetailsTop.java
+++ b/src/com/android/settings/applications/InstalledAppDetailsTop.java
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.util.FeatureFlagUtils;
import com.android.settings.SettingsActivity;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
public class InstalledAppDetailsTop extends SettingsActivity {
diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java
index c613a7b166e..ee954acf250 100644
--- a/src/com/android/settings/applications/RecentAppsPreferenceController.java
+++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java
@@ -40,6 +40,7 @@ import android.util.Log;
import com.android.settings.R;
import com.android.settings.Utils;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.AppPreference;
diff --git a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
new file mode 100644
index 00000000000..b10d06c9b55
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2017 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.settings.applications.appinfo;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+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.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.IWebViewUpdateService;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.ActionButtonPreference;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class AppActionButtonPreferenceController extends BasePreferenceController
+ implements AppInfoDashboardFragment.Callback {
+
+ private static final String TAG = "AppActionButtonControl";
+ private static final String KEY_ACTION_BUTTONS = "action_buttons";
+
+ @VisibleForTesting
+ ActionButtonPreference mActionButtons;
+ private final AppInfoDashboardFragment mParent;
+ private final String mPackageName;
+ private final HashSet mHomePackages = new HashSet<>();
+ private final ApplicationFeatureProvider mApplicationFeatureProvider;
+
+ private int mUserId;
+ private DevicePolicyManagerWrapper mDpm;
+ private UserManager mUserManager;
+ private PackageManager mPm;
+
+ 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 "
+ + mParent.getAppEntry().info.packageName + " " + enabled);
+ updateForceStopButton(enabled);
+ }
+ };
+
+ public AppActionButtonPreferenceController(Context context, AppInfoDashboardFragment parent,
+ String packageName) {
+ super(context, KEY_ACTION_BUTTONS);
+ mParent = parent;
+ mPackageName = packageName;
+ mUserId = UserHandle.myUserId();
+ mApplicationFeatureProvider = FeatureFactory.getFactory(context)
+ .getApplicationFeatureProvider(context);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS))
+ .setButton2Text(R.string.force_stop)
+ .setButton2Positive(false)
+ .setButton2Enabled(false);
+ }
+
+ @Override
+ public void refreshUi() {
+ if (mPm == null) {
+ mPm = mContext.getPackageManager();
+ }
+ if (mDpm == null) {
+ mDpm = new DevicePolicyManagerWrapper(
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ }
+ if (mUserManager == null) {
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+ final AppEntry appEntry = mParent.getAppEntry();
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+
+ // Get list of "home" apps and trace through any meta-data references
+ final List homeActivities = new ArrayList();
+ mPm.getHomeActivities(homeActivities);
+ mHomePackages.clear();
+ for (int i = 0; i< homeActivities.size(); i++) {
+ final 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);
+ }
+ }
+ }
+
+ checkForceStop(appEntry, packageInfo);
+ initUninstallButtons(appEntry, packageInfo);
+ }
+
+ @VisibleForTesting
+ void initUninstallButtons(AppEntry appEntry, PackageInfo packageInfo) {
+ final boolean isBundled = (appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ boolean enabled;
+ if (isBundled) {
+ enabled = handleDisableable(appEntry, packageInfo);
+ } else {
+ enabled = initUninstallButtonForUserApp();
+ }
+ // 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(packageInfo.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 (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, packageInfo.packageName)) {
+ enabled = false;
+ }
+
+ // Don't allow uninstalling the device provisioning package.
+ if (Utils.isDeviceProvisioningPackage(mContext.getResources(), appEntry.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(packageInfo.packageName)) {
+ if (isBundled) {
+ enabled = false;
+ } else {
+ ArrayList homeActivities = new ArrayList();
+ 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 = !packageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ }
+ }
+ }
+
+ if (RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId)) {
+ enabled = false;
+ }
+
+ try {
+ final IWebViewUpdateService webviewUpdateService =
+ IWebViewUpdateService.Stub.asInterface(
+ ServiceManager.getService("webviewupdate"));
+ if (webviewUpdateService.isFallbackPackage(appEntry.info.packageName)) {
+ enabled = false;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mActionButtons.setButton1Enabled(enabled);
+ if (enabled) {
+ // Register listener
+ mActionButtons.setButton1OnClickListener(v -> mParent.handleUninstallButtonClick());
+ }
+ }
+
+ @VisibleForTesting
+ boolean initUninstallButtonForUserApp() {
+ boolean enabled = true;
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if ((packageInfo.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;
+ } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
+ enabled = false;
+ mActionButtons.setButton1Visible(false);
+ }
+ mActionButtons.setButton1Text(R.string.uninstall_text).setButton1Positive(false);
+ return enabled;
+ }
+
+ @VisibleForTesting
+ boolean handleDisableable(AppEntry appEntry, PackageInfo packageInfo) {
+ 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(appEntry.info.packageName)
+ || Utils.isSystemPackage(mContext.getResources(), mPm, packageInfo)) {
+ // Disable button for core system applications.
+ mActionButtons
+ .setButton1Text(R.string.disable_text)
+ .setButton1Positive(false);
+ } else if (appEntry.info.enabled && appEntry.info.enabledSetting
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ mActionButtons
+ .setButton1Text(R.string.disable_text)
+ .setButton1Positive(false);
+ disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
+ .contains(appEntry.info.packageName);
+ } else {
+ mActionButtons
+ .setButton1Text(R.string.enable_text)
+ .setButton1Positive(true);
+ disableable = true;
+ }
+
+ return disableable;
+ }
+
+ private void updateForceStopButton(boolean enabled) {
+ final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ mActionButtons
+ .setButton2Enabled(disallowedBySystem ? false : enabled)
+ .setButton2OnClickListener(
+ disallowedBySystem ? null : v -> mParent.handleForceStopButtonClick());
+ }
+
+ void checkForceStop(AppEntry appEntry, PackageInfo packageInfo) {
+ if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) {
+ // User can't force stop device admin.
+ Log.w(TAG, "User can't force stop device admin");
+ updateForceStopButton(false);
+ } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
+ updateForceStopButton(false);
+ mActionButtons.setButton2Visible(false);
+ } else if ((appEntry.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");
+ updateForceStopButton(true);
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+ Uri.fromParts("package", appEntry.info.packageName, null));
+ intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { appEntry.info.packageName });
+ intent.putExtra(Intent.EXTRA_UID, appEntry.info.uid);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(appEntry.info.uid));
+ Log.d(TAG, "Sending broadcast to query restart status for "
+ + appEntry.info.packageName);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
+ }
+ }
+
+ private boolean signaturesMatch(String pkg1, String pkg2) {
+ if (pkg1 != null && pkg2 != null) {
+ try {
+ return mPm.checkSignatures(pkg1, pkg2) >= PackageManager.SIGNATURE_MATCH;
+ } catch (Exception e) {
+ // e.g. named alternate package not found during lookup;
+ // this is an expected case sometimes
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 017afe75479..ffe2bf313f9 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -32,7 +32,6 @@ import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.BatteryEntry;
diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
index 61f3e46ca8e..669bc5a3a9e 100644
--- a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
@@ -34,7 +34,6 @@ import android.text.format.Formatter;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.datausage.AppDataUsage;
import com.android.settings.datausage.DataUsageList;
import com.android.settings.datausage.DataUsageUtils;
diff --git a/src/com/android/settings/applications/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
similarity index 64%
rename from src/com/android/settings/applications/AppInfoDashboardFragment.java
rename to src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 1e24df900fc..57e2b0c1f84 100755
--- a/src/com/android/settings/applications/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -14,7 +14,7 @@
* under the License.
*/
-package com.android.settings.applications;
+package com.android.settings.applications.appinfo;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -23,10 +23,8 @@ import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,13 +33,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
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.annotation.VisibleForTesting;
@@ -51,7 +46,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.webkit.IWebViewUpdateService;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.DeviceAdminAdd;
@@ -59,32 +53,10 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
-import com.android.settings.applications.appinfo.AppBatteryPreferenceController;
-import com.android.settings.applications.appinfo.AppDataUsagePreferenceController;
-import com.android.settings.applications.appinfo.AppInstallerInfoPreferenceController;
-import com.android.settings.applications.appinfo.AppInstallerPreferenceCategoryController;
-import com.android.settings.applications.appinfo.AppMemoryPreferenceController;
-import com.android.settings.applications.appinfo.AppNotificationPreferenceController;
-import com.android.settings.applications.appinfo.AppOpenByDefaultPreferenceController;
-import com.android.settings.applications.appinfo.AppPermissionPreferenceController;
-import com.android.settings.applications.appinfo.AppStoragePreferenceController;
-import com.android.settings.applications.appinfo.AppVersionPreferenceController;
-import com.android.settings.applications.appinfo.DefaultBrowserShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultEmergencyShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultHomeShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultPhoneShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultSmsShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DrawOverlayDetailPreferenceController;
-import com.android.settings.applications.appinfo.ExternalSourceDetailPreferenceController;
-import com.android.settings.applications.appinfo.InstantAppButtonsPreferenceController;
-import com.android.settings.applications.appinfo.InstantAppDomainsPreferenceController;
-import com.android.settings.applications.appinfo.PictureInPictureDetailPreferenceController;
-import com.android.settings.applications.appinfo.WriteSystemSettingsPreferenceController;
+import com.android.settings.applications.LayoutPreference;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.widget.ActionButtonPreference;
import com.android.settings.widget.EntityHeaderController;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settings.wrapper.DevicePolicyManagerWrapper;
@@ -98,7 +70,6 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
/**
@@ -116,38 +87,37 @@ public class AppInfoDashboardFragment extends DashboardFragment
private static final String TAG = "AppInfoDashboard";
// Menu identifiers
- public static final int UNINSTALL_ALL_USERS_MENU = 1;
- public static final int UNINSTALL_UPDATES = 2;
+ private static final int UNINSTALL_ALL_USERS_MENU = 1;
+ private static final int UNINSTALL_UPDATES = 2;
// Result code identifiers
- public static final int REQUEST_UNINSTALL = 0;
+ @VisibleForTesting
+ static final int REQUEST_UNINSTALL = 0;
private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
- public static final int SUB_INFO_FRAGMENT = 1;
+ static final int SUB_INFO_FRAGMENT = 1;
- public static final int LOADER_CHART_DATA = 2;
- public static final int LOADER_STORAGE = 3;
- @VisibleForTesting
- public static final int LOADER_BATTERY = 4;
+ static final int LOADER_CHART_DATA = 2;
+ static final int LOADER_STORAGE = 3;
+ static final int LOADER_BATTERY = 4;
// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
private static final int DLG_FORCE_STOP = DLG_BASE + 1;
private static final int DLG_DISABLE = DLG_BASE + 2;
private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
+
private static final String KEY_HEADER = "header_view";
- private static final String KEY_ACTION_BUTTONS = "action_buttons";
+ private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info";
public static final String ARG_PACKAGE_NAME = "package";
public static final String ARG_PACKAGE_UID = "uid";
- protected static final boolean localLOGV = false;
- private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info";
+ private static final boolean localLOGV = false;
private EnforcedAdmin mAppsControlDisallowedAdmin;
private boolean mAppsControlDisallowedBySystem;
- private ApplicationFeatureProvider mApplicationFeatureProvider;
private ApplicationsState mState;
private ApplicationsState.Session mSession;
private ApplicationsState.AppEntry mAppEntry;
@@ -163,8 +133,6 @@ public class AppInfoDashboardFragment extends DashboardFragment
private boolean mListeningToPackageRemove;
- private final HashSet mHomePackages = new HashSet<>();
-
private boolean mInitialized;
private boolean mShowUninstalled;
private LayoutPreference mHeader;
@@ -173,10 +141,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
private List mCallbacks = new ArrayList<>();
- @VisibleForTesting
- ActionButtonPreference mActionButtons;
-
private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController;
+ private AppActionButtonPreferenceController mAppActionButtonPreferenceController;
/**
* Callback to invoke when app info has been changed.
@@ -185,139 +151,17 @@ public class AppInfoDashboardFragment extends DashboardFragment
void refreshUi();
}
- @VisibleForTesting
- boolean handleDisableable() {
- 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)
- || Utils.isSystemPackage(getContext().getResources(), mPm, mPackageInfo)) {
- // Disable button for core system applications.
- mActionButtons
- .setButton1Text(R.string.disable_text)
- .setButton1Positive(false);
- } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
- mActionButtons
- .setButton1Text(R.string.disable_text)
- .setButton1Positive(false);
- disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
- .contains(mAppEntry.info.packageName);
- } else {
- mActionButtons
- .setButton1Text(R.string.enable_text)
- .setButton1Positive(true);
- disableable = true;
- }
-
- return disableable;
- }
-
private boolean isDisabledUntilUsed() {
return mAppEntry.info.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
}
- private void initUninstallButtons() {
- final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- boolean enabled;
- if (isBundled) {
- enabled = handleDisableable();
- } else {
- enabled = initUninstallButtonForUserApp();
- }
- // 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 (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) {
- enabled = false;
- }
-
- // Don't allow uninstalling the device provisioning package.
- if (Utils.isDeviceProvisioningPackage(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 homeActivities = new ArrayList();
- 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;
- }
-
- try {
- IWebViewUpdateService webviewUpdateService =
- IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
- if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) {
- enabled = false;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
-
- mActionButtons.setButton1Enabled(enabled);
- if (enabled) {
- // Register listener
- mActionButtons.setButton1OnClickListener(v -> handleUninstallButtonClick());
- }
- }
-
- @VisibleForTesting
- boolean initUninstallButtonForUserApp() {
- boolean enabled = true;
- 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;
- } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
- enabled = false;
- mActionButtons.setButton1Visible(false);
- }
- mActionButtons.setButton1Text(R.string.uninstall_text).setButton1Positive(false);
- return enabled;
- }
-
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mFinishing = false;
final Activity activity = getActivity();
- mApplicationFeatureProvider = FeatureFactory.getFactory(activity)
- .getApplicationFeatureProvider(activity);
mDpm = new DevicePolicyManagerWrapper(
(DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE));
mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
@@ -379,6 +223,9 @@ public class AppInfoDashboardFragment extends DashboardFragment
final AppInstallerInfoPreferenceController appInstallerInfoPreferenceController =
new AppInstallerInfoPreferenceController(context, this, packageName);
controllers.add(appInstallerInfoPreferenceController);
+ mAppActionButtonPreferenceController =
+ new AppActionButtonPreferenceController(context, this, packageName);
+ controllers.add(mAppActionButtonPreferenceController);
for (AbstractPreferenceController controller : controllers) {
mCallbacks.add((Callback) controller);
@@ -414,20 +261,21 @@ public class AppInfoDashboardFragment extends DashboardFragment
return controllers;
}
- public ApplicationsState.AppEntry getAppEntry() {
+ ApplicationsState.AppEntry getAppEntry() {
if (mAppEntry == null) {
retrieveAppEntry();
}
return mAppEntry;
}
- public PackageInfo getPackageInfo() {
+ PackageInfo getPackageInfo() {
if (mAppEntry == null) {
retrieveAppEntry();
}
return mPackageInfo;
}
+ @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mFinishing) {
@@ -435,10 +283,6 @@ public class AppInfoDashboardFragment extends DashboardFragment
}
final Activity activity = getActivity();
mHeader = (LayoutPreference) findPreference(KEY_HEADER);
- mActionButtons = ((ActionButtonPreference) findPreference(KEY_ACTION_BUTTONS))
- .setButton2Text(R.string.force_stop)
- .setButton2Positive(false)
- .setButton2Enabled(false);
EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header))
.setRecyclerView(getListView(), getLifecycle())
.setPackageName(mPackageName)
@@ -492,7 +336,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
}
menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
+ final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem);
if (uninstallUpdatesItem.isVisible()) {
RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
@@ -525,7 +369,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
mDisableAfterUninstall = false;
new DisableChanger(this, mAppEntry.info,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
- .execute((Object)null);
+ .execute((Object) null);
}
// continue with following operations
case REQUEST_REMOVE_DEVICE_ADMIN:
@@ -569,7 +413,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
showIt = false;
} else if (mUserManager.getUsers().size() < 2) {
showIt = false;
- } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2
+ } else if (getNumberOfUserWithPackageInstalled(mPackageName) < 2
&& (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
showIt = false;
} else if (AppUtils.isInstant(appEntry.info)) {
@@ -578,21 +422,6 @@ public class AppInfoDashboardFragment extends DashboardFragment
return showIt;
}
- 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;
- }
-
@VisibleForTesting
boolean refreshUi() {
retrieveAppEntry();
@@ -604,31 +433,11 @@ public class AppInfoDashboardFragment extends DashboardFragment
return false; // onCreate must have failed, make sure to exit
}
- // Get list of "home" apps and trace through any meta-data references
- List homeActivities = new ArrayList();
- mPm.getHomeActivities(homeActivities);
- mHomePackages.clear();
- for (int i = 0; i< homeActivities.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);
- }
- }
- }
-
- checkForceStop();
setAppLabelAndIcon(mPackageInfo);
- initUninstallButtons();
// Update the preference summaries.
- Activity context = getActivity();
+ final Activity context = getActivity();
for (Callback callback : mCallbacks) {
callback.refreshUi();
}
@@ -641,7 +450,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
// All other times: if the app no longer exists then we want
// to go away.
try {
- ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
+ final ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
mAppEntry.info.packageName,
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_ANY_USER);
@@ -712,8 +521,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
private 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);
+ final Uri packageURI = Uri.parse("package:"+packageName);
+ final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
mMetricsFeatureProvider.action(
getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
@@ -723,66 +532,32 @@ public class AppInfoDashboardFragment extends DashboardFragment
private void forceStopPackage(String pkgName) {
mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
- ActivityManager am = (ActivityManager) getActivity().getSystemService(
+ final ActivityManager am = (ActivityManager) getActivity().getSystemService(
Context.ACTIVITY_SERVICE);
Log.d(TAG, "Stopping package " + pkgName);
am.forceStopPackage(pkgName);
- int userId = UserHandle.getUserId(mAppEntry.info.uid);
+ final int userId = UserHandle.getUserId(mAppEntry.info.uid);
mState.invalidatePackage(pkgName, userId);
- AppEntry newEnt = mState.getEntry(pkgName, userId);
+ final AppEntry newEnt = mState.getEntry(pkgName, userId);
if (newEnt != null) {
mAppEntry = newEnt;
}
- checkForceStop();
- }
-
- private void updateForceStopButton(boolean enabled) {
- mActionButtons
- .setButton2Enabled(mAppsControlDisallowedBySystem ? false : enabled)
- .setButton2OnClickListener(mAppsControlDisallowedBySystem
- ? null : v -> handleForceStopButtonClick());
- }
-
- @VisibleForTesting
- void checkForceStop() {
- if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
- // User can't force stop device admin.
- Log.w(TAG, "User can't force stop device admin");
- updateForceStopButton(false);
- } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
- updateForceStopButton(false);
- mActionButtons.setButton2Visible(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");
- updateForceStopButton(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);
- getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
- mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
- }
+ mAppActionButtonPreferenceController.checkForceStop(mAppEntry, mPackageInfo);
}
public static void startAppInfoFragment(Class> fragment, int title,
SettingsPreferenceFragment caller, AppEntry appEntry) {
// start new fragment to display extended information
- Bundle args = new Bundle();
+ final Bundle args = new Bundle();
args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName);
args.putInt(ARG_PACKAGE_UID, appEntry.info.uid);
- SettingsActivity sa = (SettingsActivity) caller.getActivity();
+ final SettingsActivity sa = (SettingsActivity) caller.getActivity();
sa.startPreferencePanel(caller, fragment.getName(), args, title, null, caller,
SUB_INFO_FRAGMENT);
}
- private void handleUninstallButtonClick() {
+ void handleUninstallButtonClick() {
if (mAppEntry == null) {
setIntentAndFinish(true, true);
return;
@@ -790,8 +565,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
final String packageName = mAppEntry.info.packageName;
if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
stopListeningToPackageRemove();
- Activity activity = getActivity();
- Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
+ final Activity activity = getActivity();
+ final Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
mPackageName);
mMetricsFeatureProvider.action(
@@ -799,9 +574,9 @@ public class AppInfoDashboardFragment extends DashboardFragment
activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN);
return;
}
- EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
+ final EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
packageName, mUserId);
- boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
+ final boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId);
if (admin != null && !uninstallBlockedBySystem) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
@@ -830,7 +605,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
}
}
- private void handleForceStopButtonClick() {
+ void handleForceStopButtonClick() {
if (mAppEntry == null) {
setIntentAndFinish(true, true);
return;
@@ -847,8 +622,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
/** 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);
+ return userCount == 1 || (mUserManager.isSplitSystemUser() && userCount == 2);
}
private void onPackageRemoved() {
@@ -856,35 +630,25 @@ public class AppInfoDashboardFragment extends DashboardFragment
getActivity().finishAndRemoveTask();
}
- /**
- * Elicit this class for testing. Test cannot be done in robolectric because it
- * invokes the new API.
- */
@VisibleForTesting
- public static class PackageUtil {
- /**
- * Count how many users in device have installed package {@paramref packageName}
- */
- public static int countPackageInUsers(PackageManager packageManager, UserManager
- userManager, String packageName) {
- final List userInfos = userManager.getUsers(true);
- int count = 0;
+ int getNumberOfUserWithPackageInstalled(String packageName) {
+ final List userInfos = mUserManager.getUsers(true);
+ int count = 0;
- for (final UserInfo userInfo : userInfos) {
- try {
- // Use this API to check whether user has this package
- final ApplicationInfo info = packageManager.getApplicationInfoAsUser(
- packageName, PackageManager.GET_META_DATA, userInfo.id);
- if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
- count++;
- }
- } catch(NameNotFoundException e) {
- Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
+ for (final UserInfo userInfo : userInfos) {
+ try {
+ // Use this API to check whether user has this package
+ final ApplicationInfo info = mPm.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userInfo.id);
+ if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
+ count++;
}
+ } catch(NameNotFoundException e) {
+ Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
}
-
- return count;
}
+
+ return count;
}
private static class DisableChanger extends AsyncTask