package com.android.settings.applications.appinfo;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.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;
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);
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
}
}
- checkForceStop(appEntry, packageInfo);
initUninstallButtons(appEntry, packageInfo);
}
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 {
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;
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;
// 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
// 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;
private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController;
private AppActionButtonPreferenceController mAppActionButtonPreferenceController;
+ private ForceStopOptionsMenuController mForceStopOptionsMenuController;
/**
* Callback to invoke when app info has been changed.
return;
}
+ mForceStopOptionsMenuController =
+ new ForceStopOptionsMenuController(activity, this /* parent */, mDpm,
+ mMetricsFeatureProvider, getLifecycle());
setHasOptionsMenu(true);
}
return mAppEntry;
}
+ void setAppEntry(ApplicationsState.AppEntry appEntry) {
+ mAppEntry = appEntry;
+ }
+
PackageInfo getPackageInfo() {
if (mAppEntry == null) {
retrieveAppEntry();
return mPackageInfo;
}
+ ApplicationsState getAppState() {
+ return mState;
+ }
+
@Override
public void onPackageSizeChanged(String packageName) {
if (!TextUtils.equals(packageName, mPackageName)) {
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);
uninstallPkg(mAppEntry.info.packageName, false, false);
return true;
}
- return false;
+ return super.onOptionsItemSelected(item);
}
@Override
})
.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);
}
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
}
}
- 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();
}
}
- 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);
--- /dev/null
+/*
+ * 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();
+ }
+
+}
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;
}
@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
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);
}
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();
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;
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)
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);
--- /dev/null
+/*
+ * 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));
+ }
+
+}