diff --git a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java index 130138c376f..1a5a285da1e 100644 --- a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java @@ -16,18 +16,14 @@ 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; @@ -35,7 +31,6 @@ 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; @@ -71,16 +66,6 @@ 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); @@ -101,9 +86,7 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS)) - .setButton2Text(R.string.force_stop) - .setButton2Positive(false) - .setButton2Enabled(false); + .setButton2Visible(false); } @Override @@ -140,7 +123,6 @@ public class AppActionButtonPreferenceController extends BasePreferenceControlle } } - checkForceStop(appEntry, packageInfo); initUninstallButtons(appEntry, packageInfo); } @@ -269,41 +251,6 @@ 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 { diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 41c1f7ce05b..a99ba6592c2 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -19,7 +19,6 @@ 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; @@ -45,19 +44,15 @@ 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.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; @@ -89,6 +84,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Menu identifiers private static final int UNINSTALL_ALL_USERS_MENU = 1; private static final int UNINSTALL_UPDATES = 2; + static final int FORCE_STOP_MENU = 3; // Result code identifiers @VisibleForTesting @@ -103,7 +99,7 @@ public class AppInfoDashboardFragment extends DashboardFragment // Dialog identifiers used in showDialog private static final int DLG_BASE = 0; - private static final int DLG_FORCE_STOP = DLG_BASE + 1; + 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; @@ -141,6 +137,7 @@ public class AppInfoDashboardFragment extends DashboardFragment private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController; private AppActionButtonPreferenceController mAppActionButtonPreferenceController; + private ForceStopOptionsMenuController mForceStopOptionsMenuController; /** * Callback to invoke when app info has been changed. @@ -172,6 +169,9 @@ public class AppInfoDashboardFragment extends DashboardFragment return; } + mForceStopOptionsMenuController = + new ForceStopOptionsMenuController(activity, this /* parent */, mDpm, + mMetricsFeatureProvider, getLifecycle()); setHasOptionsMenu(true); } @@ -268,6 +268,10 @@ public class AppInfoDashboardFragment extends DashboardFragment return mAppEntry; } + void setAppEntry(ApplicationsState.AppEntry appEntry) { + mAppEntry = appEntry; + } + PackageInfo getPackageInfo() { if (mAppEntry == null) { retrieveAppEntry(); @@ -275,6 +279,10 @@ public class AppInfoDashboardFragment extends DashboardFragment return mPackageInfo; } + ApplicationsState getAppState() { + return mState; + } + @Override public void onPackageSizeChanged(String packageName) { if (!TextUtils.equals(packageName, mPackageName)) { @@ -315,6 +323,7 @@ public class AppInfoDashboardFragment extends DashboardFragment if (mFinishing) { return; } + super.onPrepareOptionsMenu(menu); menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry)); mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); @@ -335,7 +344,7 @@ public class AppInfoDashboardFragment extends DashboardFragment uninstallPkg(mAppEntry.info.packageName, false, false); return true; } - return false; + return super.onOptionsItemSelected(item); } @Override @@ -465,18 +474,10 @@ 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); - } - }) - .setNegativeButton(R.string.dlg_cancel, null) - .create(); + } + final AlertDialog dialog = mForceStopOptionsMenuController.createDialog(id); + if (dialog != null) { + return dialog; } return mInstantAppButtonPreferenceController.createDialog(id); } @@ -493,21 +494,6 @@ 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, SettingsPreferenceFragment caller, AppEntry appEntry) { // start new fragment to display extended information @@ -568,20 +554,6 @@ 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(); @@ -679,7 +651,7 @@ public class AppInfoDashboardFragment extends DashboardFragment } } - private void setIntentAndFinish(boolean finish, boolean appChanged) { + void setIntentAndFinish(boolean finish, boolean appChanged) { if (localLOGV) Log.i(TAG, "appChanged="+appChanged); final Intent intent = new Intent(); intent.putExtra(ManageApplications.APP_CHG, appChanged); diff --git a/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java b/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java new file mode 100644 index 00000000000..cf8714795a2 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java @@ -0,0 +1,198 @@ +/* + * 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(); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java index 7d5eb318026..70b9cc972d8 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java @@ -18,27 +18,18 @@ 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; @@ -120,16 +111,14 @@ public class AppActionButtonPreferenceControllerTest { } @Test - public void displayPreference_shouldInitializeForceStopButton() { + public void displayPreference_shouldSetButton2Invisible() { 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).setButton2Positive(false); - verify(preference).setButton2Text(R.string.force_stop); - verify(preference).setButton2Enabled(false); + verify(preference).setButton2Visible(false); } @Test @@ -138,14 +127,12 @@ 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); } @@ -198,71 +185,6 @@ 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(); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java index ee870b748bd..7108ef0820b 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java @@ -42,7 +42,9 @@ import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.applications.LayoutPreference; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -53,6 +55,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -103,7 +106,8 @@ public class AppHeaderViewPreferenceControllerTest { appEntry.info = info; when(mFragment.getAppEntry()).thenReturn(appEntry); when(mFragment.getPackageInfo()).thenReturn(packageInfo); - + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); final TextView title = mHeader.findViewById(R.id.entity_header_title); final TextView summary = mHeader.findViewById(R.id.entity_header_summary); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java new file mode 100644 index 00000000000..47190086a51 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java @@ -0,0 +1,215 @@ +/* + * 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +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.doReturn; +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.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.DevicePolicyManagerWrapper; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION +) +public final class ForceStopOptionsMenuControllerTest { + + private static final String PACKAGE_NAME = "test_package_name"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UserManager mUserManager; + @Mock + private SettingsActivity mActivity; + @Mock + private DevicePolicyManagerWrapper mDevicePolicyManager; + @Mock + private PackageManager mPackageManager; + + private AppInfoDashboardFragment mFragment; + private ForceStopOptionsMenuController mController; + private Context mShadowContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mShadowContext = spy(RuntimeEnvironment.application); + mFragment = spy(new AppInfoDashboardFragment()); + ReflectionHelpers.setField(mFragment, "mDpm", mDevicePolicyManager); + ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager); + doReturn(mActivity).when(mFragment).getActivity(); + doReturn(mShadowContext).when(mFragment).getContext(); + doReturn(mPackageManager).when(mActivity).getPackageManager(); + when(mShadowContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = spy(new ForceStopOptionsMenuController( + mShadowContext, mFragment, mDevicePolicyManager, + null /* metricsFeatureProvider */, null /* lifecycle */)); + + // Default to not considering any apps to be instant (individual tests can override this). + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + } + + @Test + public void onCreateOptionsMenu_shouldAddForceStop() { + final Menu menu = mock(Menu.class); + when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mock(MenuItem.class)); + + mController.onCreateOptionsMenu(menu, null /* inflater */); + + verify(menu).add(anyInt(), eq(AppInfoDashboardFragment.FORCE_STOP_MENU), anyInt(), + eq(R.string.force_stop)); + } + + @Test + public void onPrepareOptionsMenu_shouldUpdateForceStopMenu() { + final Menu menu = mock(Menu.class); + doNothing().when(mController).updateForceStopMenu(any(), any()); + doReturn(mock(AppEntry.class)).when(mFragment).getAppEntry(); + doReturn(mock(PackageInfo.class)).when(mFragment).getPackageInfo(); + + mController.onPrepareOptionsMenu(menu); + + verify(mController).updateForceStopMenu(any(), any()); + } + + @Test + public void onOptionsItemSelected_shouldHandleForceStopMenuClick() { + doReturn(mock(AppEntry.class)).when(mFragment).getAppEntry(); + doNothing().when(mController).handleForceStopMenuClick(); + final MenuItem menu = mock(MenuItem.class); + when(menu.getItemId()).thenReturn(AppInfoDashboardFragment.FORCE_STOP_MENU); + + mController.onOptionsItemSelected(menu); + + verify(mController).handleForceStopMenuClick(); + } + + // 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 updateForceStopMenu_instantApps_shouldNotShowForceStop() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + final MenuItem forceStopMenu = mock(MenuItem.class); + ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu); + // Make this app appear to be instant. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + final PackageInfo packageInfo = mock(PackageInfo.class); + final AppEntry appEntry = mock(AppEntry.class); + final ApplicationInfo info = new ApplicationInfo(); + appEntry.info = info; + + mController.updateForceStopMenu(appEntry, packageInfo); + + verify(forceStopMenu).setVisible(false); + } + + @Test + public void updateForceStopMenu_hasActiveAdmin_shouldDisableForceStop() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + final MenuItem forceStopMenu = mock(MenuItem.class); + ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu); + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + final String packageName = "Package1"; + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + final AppEntry appEntry = mock(AppEntry.class); + when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true); + + mController.updateForceStopMenu(appEntry, packageInfo); + + verify(forceStopMenu).setEnabled(false); + } + + @Test + public void updateForceStopMenu_appRunning_shouldEnableForceStop() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + final MenuItem forceStopMenu = mock(MenuItem.class); + ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu); + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + final PackageInfo packageInfo = mock(PackageInfo.class); + final AppEntry appEntry = mock(AppEntry.class); + final ApplicationInfo info = new ApplicationInfo(); + appEntry.info = info; + + mController.updateForceStopMenu(appEntry, packageInfo); + + verify(forceStopMenu).setEnabled(true); + } + + @Test + public void updateForceStopMenu_appStopped_shouldQueryPackageRestart() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + final PackageInfo packageInfo = mock(PackageInfo.class); + final AppEntry appEntry = mock(AppEntry.class); + final ApplicationInfo info = new ApplicationInfo(); + appEntry.info = info; + info.flags = ApplicationInfo.FLAG_STOPPED; + info.packageName = "com.android.setting"; + + mController.updateForceStopMenu(appEntry, packageInfo); + + verify(mShadowContext).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)); + } + +}