Loading src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java +54 −1 Original line number Diff line number Diff line Loading @@ -16,14 +16,18 @@ 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; Loading @@ -31,6 +35,7 @@ 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; Loading Loading @@ -66,6 +71,16 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle 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); Loading @@ -86,7 +101,9 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS)) .setButton2Visible(false); .setButton2Text(R.string.force_stop) .setButton2Positive(false) .setButton2Enabled(false); } @Override Loading Loading @@ -123,6 +140,7 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle } } checkForceStop(appEntry, packageInfo); initUninstallButtons(appEntry, packageInfo); } Loading Loading @@ -251,6 +269,41 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle 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 { Loading src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +49 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.settings.applications.appinfo; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; Loading @@ -44,16 +45,20 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.DeviceAdminAdd; 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.LayoutPreference; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.PreferenceCategoryController; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.RestrictedLockUtils; Loading Loading @@ -85,8 +90,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Menu identifiers @VisibleForTesting static final int UNINSTALL_ALL_USERS_MENU = 1; @VisibleForTesting static final int UNINSTALL_UPDATES = 2; static final int FORCE_STOP_MENU = 3; static final int INSTALL_INSTANT_APP_MENU = 4; static final int INSTALL_INSTANT_APP_MENU = 3; // Result code identifiers @VisibleForTesting Loading @@ -101,7 +105,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Dialog identifiers used in showDialog private static final int DLG_BASE = 0; static final int DLG_FORCE_STOP = DLG_BASE + 1; 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; static final int DLG_CLEAR_INSTANT_APP = DLG_BASE + 4; Loading Loading @@ -140,7 +144,6 @@ public class AppInfoDashboardFragment extends DashboardFragment private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController; private AppActionButtonPreferenceController mAppActionButtonPreferenceController; private ForceStopOptionsMenuController mForceStopOptionsMenuController; /** * Callback to invoke when app info has been changed. Loading Loading @@ -171,9 +174,6 @@ public class AppInfoDashboardFragment extends DashboardFragment startListeningToPackageRemove(); mForceStopOptionsMenuController = new ForceStopOptionsMenuController(activity, this /* parent */, mDpm, mMetricsFeatureProvider, getLifecycle()); setHasOptionsMenu(true); } Loading Loading @@ -285,10 +285,6 @@ public class AppInfoDashboardFragment extends DashboardFragment return mPackageInfo; } ApplicationsState getAppState() { return mState; } @Override public void onPackageSizeChanged(String packageName) { if (!TextUtils.equals(packageName, mPackageName)) { Loading Loading @@ -488,10 +484,18 @@ public class AppInfoDashboardFragment extends DashboardFragment }) .setNegativeButton(R.string.dlg_cancel, null) .create(); case DLG_FORCE_STOP: return new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Force stop forceStopPackage(mAppEntry.info.packageName); } final AlertDialog dialog = mForceStopOptionsMenuController.createDialog(id); if (dialog != null) { return dialog; }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } return mInstantAppButtonPreferenceController.createDialog(id); } Loading @@ -508,6 +512,21 @@ public class AppInfoDashboardFragment extends DashboardFragment mDisableAfterUninstall = andDisable; } private void forceStopPackage(String pkgName) { mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); final ActivityManager am = (ActivityManager) getActivity().getSystemService( Context.ACTIVITY_SERVICE); Log.d(TAG, "Stopping package " + pkgName); am.forceStopPackage(pkgName); final int userId = UserHandle.getUserId(mAppEntry.info.uid); mState.invalidatePackage(pkgName, userId); final AppEntry newEnt = mState.getEntry(pkgName, userId); if (newEnt != null) { mAppEntry = newEnt; } mAppActionButtonPreferenceController.checkForceStop(mAppEntry, mPackageInfo); } public static void startAppInfoFragment(Class<?> fragment, int title, Bundle args, SettingsPreferenceFragment caller, AppEntry appEntry) { // start new fragment to display extended information Loading Loading @@ -573,6 +592,20 @@ public class AppInfoDashboardFragment extends DashboardFragment } } void handleForceStopButtonClick() { if (mAppEntry == null) { setIntentAndFinish(true, true); return; } if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent( getActivity(), mAppsControlDisallowedAdmin); } else { showDialogInner(DLG_FORCE_STOP, 0); //forceStopPackage(mAppInfo.packageName); } } /** Returns whether there is only one user on this device, not including the system-only user */ private boolean isSingleUser() { final int userCount = mUserManager.getUserCount(); Loading Loading @@ -671,7 +704,7 @@ public class AppInfoDashboardFragment extends DashboardFragment } } void setIntentAndFinish(boolean finish, boolean appChanged) { private void setIntentAndFinish(boolean finish, boolean appChanged) { if (localLOGV) Log.i(TAG, "appChanged="+appChanged); final Intent intent = new Intent(); intent.putExtra(ManageApplications.APP_CHG, appChanged); Loading src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.javadeleted 100644 → 0 +0 −198 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 static com.android.settings.applications.appinfo.AppInfoDashboardFragment.FORCE_STOP_MENU; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.net.Uri; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu; import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected; import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu; public class ForceStopOptionsMenuController implements LifecycleObserver, OnCreateOptionsMenu, OnPrepareOptionsMenu, OnOptionsItemSelected { private static final String TAG = "ForceStopMenuController"; private final Context mContext; private final AppInfoDashboardFragment mParent; private final DevicePolicyManagerWrapper mDpm; private final MetricsFeatureProvider mMetricsFeatureProvider; private int mUserId; private MenuItem mForceStopMenu; 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); enableForceStopMenu(enabled); } }; public ForceStopOptionsMenuController(Context context, AppInfoDashboardFragment parent, DevicePolicyManagerWrapper devicePolicyManager, MetricsFeatureProvider metricsFeatureProvider, Lifecycle lifecycle) { mContext = context; mParent = parent; mDpm = devicePolicyManager; mMetricsFeatureProvider = metricsFeatureProvider; mUserId = UserHandle.myUserId(); if (lifecycle != null) { lifecycle.addObserver(this); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.add(0, FORCE_STOP_MENU, 2, R.string.force_stop) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { if (menuItem.getItemId() == FORCE_STOP_MENU) { handleForceStopMenuClick(); return true; } return false; } @Override public void onPrepareOptionsMenu(Menu menu) { mForceStopMenu = menu.findItem(FORCE_STOP_MENU); updateForceStopMenu(mParent.getAppEntry(), mParent.getPackageInfo()); } @VisibleForTesting void updateForceStopMenu(AppEntry appEntry, PackageInfo packageInfo) { boolean enabled = false; if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) { // User can't force stop device admin. Log.w(TAG, "User can't force stop device admin"); } else if (AppUtils.isInstant(packageInfo.applicationInfo)) { // No force stop for instant app if (mForceStopMenu != null) { mForceStopMenu.setVisible(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"); enabled = 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); } enableForceStopMenu(enabled); } private void enableForceStopMenu(boolean enabled) { if (mForceStopMenu != null) { final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); mForceStopMenu.setEnabled(disallowedBySystem ? false : enabled); } } @VisibleForTesting void handleForceStopMenuClick() { if (mParent.getAppEntry() == null) { mParent.setIntentAndFinish(true, true); return; } final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); if (admin != null && !disallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, admin); } else { mParent.showDialogInner(mParent.DLG_FORCE_STOP, 0); } } private void forceStopPackage(String pkgName) { mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); final ActivityManager am = (ActivityManager) mContext.getSystemService( Context.ACTIVITY_SERVICE); Log.d(TAG, "Stopping package " + pkgName); am.forceStopPackage(pkgName); final int userId = UserHandle.getUserId(mParent.getAppEntry().info.uid); final ApplicationsState appState = mParent.getAppState(); appState.invalidatePackage(pkgName, userId); final AppEntry newEnt = appState.getEntry(pkgName, userId); if (newEnt != null) { mParent.setAppEntry(newEnt); } } public AlertDialog createDialog(int id) { if (id != mParent.DLG_FORCE_STOP) { return null; } return new AlertDialog.Builder(mContext) .setTitle(mContext.getText(R.string.force_stop_dlg_title)) .setMessage(mContext.getText(R.string.force_stop_dlg_text)) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Force stop forceStopPackage(mParent.getAppEntry().info.packageName); } }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } } tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java +80 −2 Original line number Diff line number Diff line Loading @@ -18,18 +18,27 @@ package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; 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.UserInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.support.v7.preference.PreferenceScreen; Loading Loading @@ -111,14 +120,16 @@ public class AppActionButtonPreferenceControllerTest { } @Test public void displayPreference_shouldSetButton2Invisible() { public void displayPreference_shouldInitializeForceStopButton() { final PreferenceScreen screen = mock(PreferenceScreen.class); final ActionButtonPreference preference = spy(new ActionButtonPreference(mContext)); when(screen.findPreference(mController.getPreferenceKey())).thenReturn(preference); mController.displayPreference(screen); verify(preference).setButton2Visible(false); verify(preference).setButton2Positive(false); verify(preference).setButton2Text(R.string.force_stop); verify(preference).setButton2Enabled(false); } @Test Loading @@ -127,12 +138,14 @@ public class AppActionButtonPreferenceControllerTest { final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; doNothing().when(mController).checkForceStop(appEntry, packageInfo); doNothing().when(mController).initUninstallButtons(appEntry, packageInfo); when(mFragment.getAppEntry()).thenReturn(appEntry); when(mFragment.getPackageInfo()).thenReturn(packageInfo); mController.refreshUi(); verify(mController).checkForceStop(appEntry, packageInfo); verify(mController).initUninstallButtons(appEntry, packageInfo); } Loading Loading @@ -185,6 +198,71 @@ public class AppActionButtonPreferenceControllerTest { assertThat(mController.initUninstallButtonForUserApp()).isFalse(); } // Tests that we don't show the force stop button for instant apps (they aren't allowed to run // when they aren't in the foreground). @Test public void checkForceStop_instantApps_shouldNotShowForceStop() { // Make this app appear to be instant. ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> true)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Visible(false); } @Test public void checkForceStop_hasActiveAdmin_shouldDisableForceStop() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final String packageName = "Package1"; final PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true); mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Enabled(false); } @Test public void checkForceStop_appRunning_shouldEnableForceStop() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Enabled(true); } @Test public void checkForceStop_appStopped_shouldQueryPackageRestart() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; info.flags = ApplicationInfo.FLAG_STOPPED; info.packageName = "com.android.setting"; mController.checkForceStop(appEntry, packageInfo); verify(mContext).sendOrderedBroadcastAsUser(argThat(intent-> intent != null && intent.getAction().equals(Intent.ACTION_QUERY_PACKAGE_RESTART)), any(UserHandle.class), nullable(String.class), any(BroadcastReceiver.class), nullable(Handler.class), anyInt(), nullable(String.class), nullable(Bundle.class)); } @Test public void handleDisableable_appIsHomeApp_buttonShouldNotWork() { final ApplicationInfo info = new ApplicationInfo(); Loading tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java +1 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java +54 −1 Original line number Diff line number Diff line Loading @@ -16,14 +16,18 @@ 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; Loading @@ -31,6 +35,7 @@ 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; Loading Loading @@ -66,6 +71,16 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle 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); Loading @@ -86,7 +101,9 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS)) .setButton2Visible(false); .setButton2Text(R.string.force_stop) .setButton2Positive(false) .setButton2Enabled(false); } @Override Loading Loading @@ -123,6 +140,7 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle } } checkForceStop(appEntry, packageInfo); initUninstallButtons(appEntry, packageInfo); } Loading Loading @@ -251,6 +269,41 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle 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 { Loading
src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +49 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.settings.applications.appinfo; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; Loading @@ -44,16 +45,20 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.DeviceAdminAdd; 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.LayoutPreference; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.PreferenceCategoryController; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.RestrictedLockUtils; Loading Loading @@ -85,8 +90,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Menu identifiers @VisibleForTesting static final int UNINSTALL_ALL_USERS_MENU = 1; @VisibleForTesting static final int UNINSTALL_UPDATES = 2; static final int FORCE_STOP_MENU = 3; static final int INSTALL_INSTANT_APP_MENU = 4; static final int INSTALL_INSTANT_APP_MENU = 3; // Result code identifiers @VisibleForTesting Loading @@ -101,7 +105,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Dialog identifiers used in showDialog private static final int DLG_BASE = 0; static final int DLG_FORCE_STOP = DLG_BASE + 1; 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; static final int DLG_CLEAR_INSTANT_APP = DLG_BASE + 4; Loading Loading @@ -140,7 +144,6 @@ public class AppInfoDashboardFragment extends DashboardFragment private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController; private AppActionButtonPreferenceController mAppActionButtonPreferenceController; private ForceStopOptionsMenuController mForceStopOptionsMenuController; /** * Callback to invoke when app info has been changed. Loading Loading @@ -171,9 +174,6 @@ public class AppInfoDashboardFragment extends DashboardFragment startListeningToPackageRemove(); mForceStopOptionsMenuController = new ForceStopOptionsMenuController(activity, this /* parent */, mDpm, mMetricsFeatureProvider, getLifecycle()); setHasOptionsMenu(true); } Loading Loading @@ -285,10 +285,6 @@ public class AppInfoDashboardFragment extends DashboardFragment return mPackageInfo; } ApplicationsState getAppState() { return mState; } @Override public void onPackageSizeChanged(String packageName) { if (!TextUtils.equals(packageName, mPackageName)) { Loading Loading @@ -488,10 +484,18 @@ public class AppInfoDashboardFragment extends DashboardFragment }) .setNegativeButton(R.string.dlg_cancel, null) .create(); case DLG_FORCE_STOP: return new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Force stop forceStopPackage(mAppEntry.info.packageName); } final AlertDialog dialog = mForceStopOptionsMenuController.createDialog(id); if (dialog != null) { return dialog; }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } return mInstantAppButtonPreferenceController.createDialog(id); } Loading @@ -508,6 +512,21 @@ public class AppInfoDashboardFragment extends DashboardFragment mDisableAfterUninstall = andDisable; } private void forceStopPackage(String pkgName) { mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); final ActivityManager am = (ActivityManager) getActivity().getSystemService( Context.ACTIVITY_SERVICE); Log.d(TAG, "Stopping package " + pkgName); am.forceStopPackage(pkgName); final int userId = UserHandle.getUserId(mAppEntry.info.uid); mState.invalidatePackage(pkgName, userId); final AppEntry newEnt = mState.getEntry(pkgName, userId); if (newEnt != null) { mAppEntry = newEnt; } mAppActionButtonPreferenceController.checkForceStop(mAppEntry, mPackageInfo); } public static void startAppInfoFragment(Class<?> fragment, int title, Bundle args, SettingsPreferenceFragment caller, AppEntry appEntry) { // start new fragment to display extended information Loading Loading @@ -573,6 +592,20 @@ public class AppInfoDashboardFragment extends DashboardFragment } } void handleForceStopButtonClick() { if (mAppEntry == null) { setIntentAndFinish(true, true); return; } if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent( getActivity(), mAppsControlDisallowedAdmin); } else { showDialogInner(DLG_FORCE_STOP, 0); //forceStopPackage(mAppInfo.packageName); } } /** Returns whether there is only one user on this device, not including the system-only user */ private boolean isSingleUser() { final int userCount = mUserManager.getUserCount(); Loading Loading @@ -671,7 +704,7 @@ public class AppInfoDashboardFragment extends DashboardFragment } } void setIntentAndFinish(boolean finish, boolean appChanged) { private void setIntentAndFinish(boolean finish, boolean appChanged) { if (localLOGV) Log.i(TAG, "appChanged="+appChanged); final Intent intent = new Intent(); intent.putExtra(ManageApplications.APP_CHG, appChanged); Loading
src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.javadeleted 100644 → 0 +0 −198 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 static com.android.settings.applications.appinfo.AppInfoDashboardFragment.FORCE_STOP_MENU; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.net.Uri; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu; import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected; import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu; public class ForceStopOptionsMenuController implements LifecycleObserver, OnCreateOptionsMenu, OnPrepareOptionsMenu, OnOptionsItemSelected { private static final String TAG = "ForceStopMenuController"; private final Context mContext; private final AppInfoDashboardFragment mParent; private final DevicePolicyManagerWrapper mDpm; private final MetricsFeatureProvider mMetricsFeatureProvider; private int mUserId; private MenuItem mForceStopMenu; 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); enableForceStopMenu(enabled); } }; public ForceStopOptionsMenuController(Context context, AppInfoDashboardFragment parent, DevicePolicyManagerWrapper devicePolicyManager, MetricsFeatureProvider metricsFeatureProvider, Lifecycle lifecycle) { mContext = context; mParent = parent; mDpm = devicePolicyManager; mMetricsFeatureProvider = metricsFeatureProvider; mUserId = UserHandle.myUserId(); if (lifecycle != null) { lifecycle.addObserver(this); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.add(0, FORCE_STOP_MENU, 2, R.string.force_stop) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { if (menuItem.getItemId() == FORCE_STOP_MENU) { handleForceStopMenuClick(); return true; } return false; } @Override public void onPrepareOptionsMenu(Menu menu) { mForceStopMenu = menu.findItem(FORCE_STOP_MENU); updateForceStopMenu(mParent.getAppEntry(), mParent.getPackageInfo()); } @VisibleForTesting void updateForceStopMenu(AppEntry appEntry, PackageInfo packageInfo) { boolean enabled = false; if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) { // User can't force stop device admin. Log.w(TAG, "User can't force stop device admin"); } else if (AppUtils.isInstant(packageInfo.applicationInfo)) { // No force stop for instant app if (mForceStopMenu != null) { mForceStopMenu.setVisible(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"); enabled = 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); } enableForceStopMenu(enabled); } private void enableForceStopMenu(boolean enabled) { if (mForceStopMenu != null) { final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); mForceStopMenu.setEnabled(disallowedBySystem ? false : enabled); } } @VisibleForTesting void handleForceStopMenuClick() { if (mParent.getAppEntry() == null) { mParent.setIntentAndFinish(true, true); return; } final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId); if (admin != null && !disallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, admin); } else { mParent.showDialogInner(mParent.DLG_FORCE_STOP, 0); } } private void forceStopPackage(String pkgName) { mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); final ActivityManager am = (ActivityManager) mContext.getSystemService( Context.ACTIVITY_SERVICE); Log.d(TAG, "Stopping package " + pkgName); am.forceStopPackage(pkgName); final int userId = UserHandle.getUserId(mParent.getAppEntry().info.uid); final ApplicationsState appState = mParent.getAppState(); appState.invalidatePackage(pkgName, userId); final AppEntry newEnt = appState.getEntry(pkgName, userId); if (newEnt != null) { mParent.setAppEntry(newEnt); } } public AlertDialog createDialog(int id) { if (id != mParent.DLG_FORCE_STOP) { return null; } return new AlertDialog.Builder(mContext) .setTitle(mContext.getText(R.string.force_stop_dlg_title)) .setMessage(mContext.getText(R.string.force_stop_dlg_text)) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Force stop forceStopPackage(mParent.getAppEntry().info.packageName); } }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } }
tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java +80 −2 Original line number Diff line number Diff line Loading @@ -18,18 +18,27 @@ package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; 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.UserInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.support.v7.preference.PreferenceScreen; Loading Loading @@ -111,14 +120,16 @@ public class AppActionButtonPreferenceControllerTest { } @Test public void displayPreference_shouldSetButton2Invisible() { public void displayPreference_shouldInitializeForceStopButton() { final PreferenceScreen screen = mock(PreferenceScreen.class); final ActionButtonPreference preference = spy(new ActionButtonPreference(mContext)); when(screen.findPreference(mController.getPreferenceKey())).thenReturn(preference); mController.displayPreference(screen); verify(preference).setButton2Visible(false); verify(preference).setButton2Positive(false); verify(preference).setButton2Text(R.string.force_stop); verify(preference).setButton2Enabled(false); } @Test Loading @@ -127,12 +138,14 @@ public class AppActionButtonPreferenceControllerTest { final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; doNothing().when(mController).checkForceStop(appEntry, packageInfo); doNothing().when(mController).initUninstallButtons(appEntry, packageInfo); when(mFragment.getAppEntry()).thenReturn(appEntry); when(mFragment.getPackageInfo()).thenReturn(packageInfo); mController.refreshUi(); verify(mController).checkForceStop(appEntry, packageInfo); verify(mController).initUninstallButtons(appEntry, packageInfo); } Loading Loading @@ -185,6 +198,71 @@ public class AppActionButtonPreferenceControllerTest { assertThat(mController.initUninstallButtonForUserApp()).isFalse(); } // Tests that we don't show the force stop button for instant apps (they aren't allowed to run // when they aren't in the foreground). @Test public void checkForceStop_instantApps_shouldNotShowForceStop() { // Make this app appear to be instant. ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> true)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Visible(false); } @Test public void checkForceStop_hasActiveAdmin_shouldDisableForceStop() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final String packageName = "Package1"; final PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true); mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Enabled(false); } @Test public void checkForceStop_appRunning_shouldEnableForceStop() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; mController.checkForceStop(appEntry, packageInfo); verify(mController.mActionButtons).setButton2Enabled(true); } @Test public void checkForceStop_appStopped_shouldQueryPackageRestart() { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (i -> false)); final PackageInfo packageInfo = mock(PackageInfo.class); final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); final ApplicationInfo info = new ApplicationInfo(); appEntry.info = info; info.flags = ApplicationInfo.FLAG_STOPPED; info.packageName = "com.android.setting"; mController.checkForceStop(appEntry, packageInfo); verify(mContext).sendOrderedBroadcastAsUser(argThat(intent-> intent != null && intent.getAction().equals(Intent.ACTION_QUERY_PACKAGE_RESTART)), any(UserHandle.class), nullable(String.class), any(BroadcastReceiver.class), nullable(Handler.class), anyInt(), nullable(String.class), nullable(Bundle.class)); } @Test public void handleDisableable_appIsHomeApp_buttonShouldNotWork() { final ApplicationInfo info = new ApplicationInfo(); Loading
tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java +1 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes