OSDN Git Service

Move force stop button into option menu.
authorDoris Ling <dling@google.com>
Fri, 19 Jan 2018 22:43:34 +0000 (14:43 -0800)
committerDoris Ling <dling@google.com>
Mon, 29 Jan 2018 23:44:23 +0000 (15:44 -0800)
- only show 1 button in the app info page
- move all handling for the force stop button from
AppActionButtonPreferenceController to a new option menu controller

Bug: 71778950
Test: make RunSettingsRoboTests
Change-Id: Iaa2c9784162a5f1acaaf9e11e3ce988945b40538

src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java
tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java [new file with mode: 0644]

index 130138c..1a5a285 100644 (file)
 
 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 {
index 41c1f7c..a99ba65 100755 (executable)
@@ -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 (file)
index 0000000..cf87147
--- /dev/null
@@ -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();
+    }
+
+}
index 7d5eb31..70b9cc9 100644 (file)
@@ -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();
index ee870b7..7108ef0 100644 (file)
@@ -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 (file)
index 0000000..4719008
--- /dev/null
@@ -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));
+    }
+
+}