From 77ee0b08ddb8a348a051f19a349f371bf517905d Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 31 May 2017 10:42:19 -0700 Subject: [PATCH] Add method to disable "disable" button in installed app UI OEMs can now force grey out the "disable" button for important apps on device. Change-Id: I8ea431b52e50e424be55946f70175db5412e2f4a Merged-In: Ic075a07ad12592bd60238c7b1c9ab84932c8db3c Fix: 38250742 Test: make RunSettingsRoboTests --- .../settings/applications/AppInfoBase.java | 15 ++- .../ApplicationFeatureProvider.java | 6 + .../ApplicationFeatureProviderImpl.java | 5 + .../applications/InstalledAppDetails.java | 14 +- .../ApplicationFeatureProviderImplTest.java | 6 + .../applications/InstalledAppDetailsTest.java | 122 ++++++++++++++---- 6 files changed, 133 insertions(+), 35 deletions(-) diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java index b86f2f49477..a93bfbd4fef 100644 --- a/src/com/android/settings/applications/AppInfoBase.java +++ b/src/com/android/settings/applications/AppInfoBase.java @@ -42,6 +42,7 @@ import com.android.settings.SettingsActivity; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -62,6 +63,7 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment protected EnforcedAdmin mAppsControlDisallowedAdmin; protected boolean mAppsControlDisallowedBySystem; + protected ApplicationFeatureProvider mApplicationFeatureProvider; protected ApplicationsState mState; protected ApplicationsState.Session mSession; protected ApplicationsState.AppEntry mAppEntry; @@ -84,13 +86,14 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFinishing = false; - - mState = ApplicationsState.getInstance(getActivity().getApplication()); + final Activity activity = getActivity(); + mApplicationFeatureProvider = FeatureFactory.getFactory(activity) + .getApplicationFeatureProvider(activity); + mState = ApplicationsState.getInstance(activity.getApplication()); mSession = mState.newSession(this); - Context context = getActivity(); - mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); - mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - mPm = context.getPackageManager(); + mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE); + mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); + mPm = activity.getPackageManager(); IBinder b = ServiceManager.getService(Context.USB_SERVICE); mUsbManager = IUsbManager.Stub.asInterface(b); diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java index 3266fe0d7f8..27cac641c3d 100644 --- a/src/com/android/settings/applications/ApplicationFeatureProvider.java +++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java @@ -24,6 +24,7 @@ import android.view.View; import com.android.settings.applications.instantapps.InstantAppButtonsController; import java.util.List; +import java.util.Set; public interface ApplicationFeatureProvider { @@ -93,6 +94,11 @@ public interface ApplicationFeatureProvider { */ List findPersistentPreferredActivities(@UserIdInt int userId, Intent[] intents); + /** + * Returns a list of package names that should be kept enabled. + */ + Set getKeepEnabledPackages(); + /** * Callback that receives the number of packages installed on the device. */ diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java index a74479239ce..685bd2045c9 100644 --- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java +++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java @@ -135,6 +135,11 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide return preferredActivities; } + @Override + public Set getKeepEnabledPackages() { + return new ArraySet<>(); + } + private static class CurrentUserAndManagedProfilePolicyInstalledAppCounter extends InstalledAppCounter { private NumberOfAppsCallback mCallback; diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index e88e3babfe7..083681a5238 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -16,8 +16,6 @@ package com.android.settings.applications; -import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; - import android.Manifest.permission; import android.app.Activity; import android.app.ActivityManager; @@ -117,6 +115,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + /** * Activity to display application information from Settings. This activity presents * extended information associated with a package like code, data, total size, permissions @@ -232,7 +232,8 @@ public class InstalledAppDetails extends AppInfoBase } }; - private boolean handleDisableable(Button button) { + @VisibleForTesting + boolean handleDisableable(Button button) { boolean disableable = false; // Try to prevent the user from bricking their phone // by not allowing disabling of apps signed with the @@ -243,7 +244,8 @@ public class InstalledAppDetails extends AppInfoBase button.setText(R.string.disable_text); } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { button.setText(R.string.disable_text); - disableable = true; + disableable = !mApplicationFeatureProvider.getKeepEnabledPackages() + .contains(mAppEntry.info.packageName); } else { button.setText(R.string.enable_text); disableable = true; @@ -1221,9 +1223,7 @@ public class InstalledAppDetails extends AppInfoBase void maybeAddInstantAppButtons() { if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS); - final Activity activity = getActivity(); - mInstantAppButtonsController = FeatureFactory.getFactory(activity) - .getApplicationFeatureProvider(activity) + mInstantAppButtonsController = mApplicationFeatureProvider .newInstantAppButtonsController(this, buttons.findViewById(R.id.instant_app_button_container), id -> showDialogInner(id, 0)) diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java index 4c4ec46cce6..672c5b7c03d 100644 --- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java @@ -245,6 +245,12 @@ public final class ApplicationFeatureProviderImplTest { .isEqualTo(expectedManagedUserActivities); } + @Test + public void getKeepEnabledPackages_shouldContainNothing() { + assertThat(mProvider.getKeepEnabledPackages()) + .isEmpty(); + } + private void setUpUsersAndInstalledApps() { when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java index f60223673a4..98c28bf5640 100644 --- a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java @@ -16,21 +16,6 @@ package com.android.settings.applications; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyDouble; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import android.app.AlertDialog; import android.app.admin.DevicePolicyManager; import android.content.Context; @@ -38,6 +23,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.os.BatteryStats; import android.os.UserManager; import android.support.v7.preference.Preference; @@ -55,6 +41,7 @@ import com.android.settings.TestConfig; import com.android.settings.applications.instantapps.InstantAppButtonsController; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.Utils; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; @@ -68,11 +55,27 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -86,8 +89,6 @@ public final class InstalledAppDetailsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; - @Mock - ApplicationFeatureProvider mApplicationFeatureProvider; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private UserManager mUserManager; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -106,15 +107,18 @@ public final class InstalledAppDetailsTest { private PackageManager mPackageManager; @Mock private BatteryUtils mBatteryUtils; + private FakeFeatureFactory mFeatureFactory; private InstalledAppDetails mAppDetail; private Context mShadowContext; private Preference mBatteryPreference; + @Before public void setUp() { MockitoAnnotations.initMocks(this); - + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); mShadowContext = RuntimeEnvironment.application; mAppDetail = spy(new InstalledAppDetails()); mAppDetail.mBatteryUtils = mBatteryUtils; @@ -346,15 +350,14 @@ public final class InstalledAppDetailsTest { final InstalledAppDetailsWithMockInstantButtons fragment = new InstalledAppDetailsWithMockInstantButtons(); ReflectionHelpers.setField(fragment, "mPackageInfo", packageInfo); + ReflectionHelpers.setField(fragment, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); final InstantAppButtonsController buttonsController = mock(InstantAppButtonsController.class); when(buttonsController.setPackageName(anyString())).thenReturn(buttonsController); - FakeFeatureFactory.setupForTest(mContext); - FakeFeatureFactory factory = - (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); - when(factory.applicationFeatureProvider.newInstantAppButtonsController( + when(mFeatureFactory.applicationFeatureProvider.newInstantAppButtonsController( any(), any(), any())).thenReturn(buttonsController); fragment.maybeAddInstantAppButtons(); @@ -458,4 +461,79 @@ public final class InstalledAppDetailsTest { public void isBatteryStatsAvailable_parametersNull_returnFalse() { assertThat(mAppDetail.isBatteryStatsAvailable()).isFalse(); } + + @Test + public void handleDisableable_appIsHomeApp_buttonShouldNotWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final HashSet homePackages = new HashSet<>(); + homePackages.add(info.packageName); + + ReflectionHelpers.setField(mAppDetail, "mHomePackages", homePackages); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + final Button button = mock(Button.class); + + assertThat(mAppDetail.handleDisableable(button)).isFalse(); + verify(button).setText(R.string.disable_text); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void handleDisableable_appIsEnabled_buttonShouldWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn( + new HashSet<>()); + + ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + final Button button = mock(Button.class); + + assertThat(mAppDetail.handleDisableable(button)).isTrue(); + verify(button).setText(R.string.disable_text); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void handleDisableable_appIsEnabledAndInKeepEnabledWhitelist_buttonShouldNotWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + + final HashSet packages = new HashSet<>(); + packages.add(info.packageName); + when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn( + packages); + + ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + final Button button = mock(Button.class); + + assertThat(mAppDetail.handleDisableable(button)).isFalse(); + verify(button).setText(R.string.disable_text); + } + + @Implements(Utils.class) + public static class ShadowUtils { + @Implementation + public static boolean isSystemPackage(Resources resources, PackageManager pm, + PackageInfo pkg) { + return false; + } + } }