1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.applications.appinfo; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyBoolean; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.ArgumentMatchers.anyString; 25 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.Mockito.doAnswer; 27 import static org.mockito.Mockito.doNothing; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.doThrow; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.spy; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.app.ActivityManager; 37 import android.app.Application; 38 import android.app.admin.DevicePolicyManager; 39 import android.app.settings.SettingsEnums; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.om.OverlayInfo; 43 import android.content.om.OverlayManager; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.PackageInfo; 46 import android.content.pm.PackageManager; 47 import android.os.RemoteException; 48 import android.os.UserManager; 49 import android.util.ArraySet; 50 import android.view.View; 51 52 import androidx.preference.PreferenceScreen; 53 54 import com.android.settings.R; 55 import com.android.settings.SettingsActivity; 56 import com.android.settings.core.InstrumentedPreferenceFragment; 57 import com.android.settings.testutils.FakeFeatureFactory; 58 import com.android.settingslib.applications.AppUtils; 59 import com.android.settingslib.applications.ApplicationsState; 60 import com.android.settingslib.applications.instantapps.InstantAppDataProvider; 61 import com.android.settingslib.core.lifecycle.Lifecycle; 62 import com.android.settingslib.widget.ActionButtonsPreference; 63 64 import org.junit.After; 65 import org.junit.Before; 66 import org.junit.Test; 67 import org.junit.runner.RunWith; 68 import org.mockito.Answers; 69 import org.mockito.ArgumentCaptor; 70 import org.mockito.Mock; 71 import org.mockito.MockitoAnnotations; 72 import org.mockito.stubbing.Answer; 73 import org.robolectric.RobolectricTestRunner; 74 import org.robolectric.RuntimeEnvironment; 75 import org.robolectric.annotation.Config; 76 import org.robolectric.annotation.Implementation; 77 import org.robolectric.annotation.Implements; 78 import org.robolectric.annotation.Resetter; 79 import org.robolectric.util.ReflectionHelpers; 80 81 import java.util.Set; 82 83 @RunWith(RobolectricTestRunner.class) 84 public class AppButtonsPreferenceControllerTest { 85 86 private static final String PACKAGE_NAME = "com.android.settings"; 87 private static final String RRO_PACKAGE_NAME = "com.android.settings.overlay"; 88 private static final String RESOURCE_STRING = "string"; 89 private static final boolean ALL_USERS = false; 90 private static final boolean DISABLE_AFTER_INSTALL = true; 91 private static final int REQUEST_UNINSTALL = 0; 92 private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; 93 private static final OverlayInfo OVERLAY_DISABLED = createFakeOverlay("overlay", false, 1); 94 private static final OverlayInfo OVERLAY_ENABLED = createFakeOverlay("overlay", true, 1); 95 96 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 97 private SettingsActivity mSettingsActivity; 98 @Mock 99 private TestFragment mFragment; 100 @Mock 101 private Lifecycle mLifecycle; 102 @Mock 103 private ApplicationsState mState; 104 @Mock 105 private ApplicationsState.AppEntry mAppEntry; 106 @Mock 107 private ApplicationInfo mAppInfo; 108 @Mock 109 private OverlayManager mOverlayManager; 110 @Mock 111 private PackageManager mPackageManger; 112 @Mock 113 private DevicePolicyManager mDpm; 114 @Mock 115 private ActivityManager mAm; 116 @Mock 117 private UserManager mUserManager; 118 @Mock 119 private PackageInfo mPackageInfo; 120 121 private Context mContext; 122 private Intent mUninstallIntent; 123 private ActionButtonsPreference mButtonPrefs; 124 private AppButtonsPreferenceController mController; 125 126 @Before setUp()127 public void setUp() { 128 MockitoAnnotations.initMocks(this); 129 130 FakeFeatureFactory.setupForTest(); 131 mContext = RuntimeEnvironment.application; 132 doReturn(mDpm).when(mSettingsActivity).getSystemService(Context.DEVICE_POLICY_SERVICE); 133 doReturn(mUserManager).when(mSettingsActivity).getSystemService(Context.USER_SERVICE); 134 doReturn(mPackageManger).when(mSettingsActivity).getPackageManager(); 135 doReturn(mAm).when(mSettingsActivity).getSystemService(Context.ACTIVITY_SERVICE); 136 doReturn(mOverlayManager).when(mSettingsActivity). 137 getSystemService(OverlayManager.class); 138 doReturn(mAppEntry).when(mState).getEntry(anyString(), anyInt()); 139 doReturn(mContext).when(mSettingsActivity).getApplicationContext(); 140 when(mSettingsActivity.getResources().getString(anyInt())).thenReturn(RESOURCE_STRING); 141 142 mController = spy(new AppButtonsPreferenceController(mSettingsActivity, mFragment, 143 mLifecycle, PACKAGE_NAME, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN)); 144 145 mAppEntry.info = mAppInfo; 146 mAppInfo.packageName = PACKAGE_NAME; 147 mAppInfo.flags = 0; 148 mPackageInfo.packageName = PACKAGE_NAME; 149 mPackageInfo.applicationInfo = mAppInfo; 150 151 mButtonPrefs = createMock(); 152 mController.mButtonsPref = mButtonPrefs; 153 mController.mPackageInfo = mPackageInfo; 154 155 final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 156 Answer<Void> callable = invocation -> { 157 mUninstallIntent = captor.getValue(); 158 return null; 159 }; 160 doAnswer(callable).when(mFragment).startActivityForResult(captor.capture(), anyInt()); 161 } 162 163 @After tearDown()164 public void tearDown() { 165 ShadowAppUtils.reset(); 166 } 167 168 @Test 169 @Config(shadows = ShadowAppUtils.class) isAvailable_validPackageName_isTrue()170 public void isAvailable_validPackageName_isTrue() { 171 assertThat(mController.isAvailable()).isTrue(); 172 } 173 174 @Test isAvailable_nullPackageName_isFalse()175 public void isAvailable_nullPackageName_isFalse() { 176 final AppButtonsPreferenceController controller = spy( 177 new AppButtonsPreferenceController(mSettingsActivity, mFragment, 178 mLifecycle, null, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN)); 179 180 assertThat(controller.isAvailable()).isFalse(); 181 } 182 183 @Test retrieveAppEntry_hasAppEntry_notNull()184 public void retrieveAppEntry_hasAppEntry_notNull() 185 throws PackageManager.NameNotFoundException { 186 doReturn(mPackageInfo).when(mPackageManger).getPackageInfo(anyString(), anyInt()); 187 188 mController.retrieveAppEntry(); 189 190 assertThat(mController.mAppEntry).isNotNull(); 191 assertThat(mController.mPackageInfo).isNotNull(); 192 } 193 194 @Test retrieveAppEntry_noAppEntry_null()195 public void retrieveAppEntry_noAppEntry_null() throws PackageManager.NameNotFoundException { 196 doReturn(null).when(mState).getEntry(eq(PACKAGE_NAME), anyInt()); 197 doReturn(mPackageInfo).when(mPackageManger).getPackageInfo(anyString(), anyInt()); 198 199 mController.retrieveAppEntry(); 200 201 assertThat(mController.mAppEntry).isNull(); 202 assertThat(mController.mPackageInfo).isNull(); 203 } 204 205 @Test retrieveAppEntry_throwException_null()206 public void retrieveAppEntry_throwException_null() throws 207 PackageManager.NameNotFoundException { 208 doReturn(mAppEntry).when(mState).getEntry(anyString(), anyInt()); 209 doThrow(new PackageManager.NameNotFoundException()).when(mPackageManger).getPackageInfo( 210 anyString(), anyInt()); 211 212 mController.retrieveAppEntry(); 213 214 assertThat(mController.mAppEntry).isNotNull(); 215 assertThat(mController.mPackageInfo).isNull(); 216 } 217 218 @Test updateOpenButton_noLaunchIntent_buttonShouldBeDisable()219 public void updateOpenButton_noLaunchIntent_buttonShouldBeDisable() { 220 mController.updateOpenButton(); 221 222 verify(mButtonPrefs).setButton1Visible(false); 223 } 224 225 @Test updateOpenButton_haveLaunchIntent_buttonShouldBeEnable()226 public void updateOpenButton_haveLaunchIntent_buttonShouldBeEnable() { 227 doReturn(new Intent()).when(mPackageManger).getLaunchIntentForPackage(anyString()); 228 229 mController.updateOpenButton(); 230 231 verify(mButtonPrefs).setButton1Visible(true); 232 } 233 234 @Test updateUninstallButton_isSystemApp_handleAsDisableableButton()235 public void updateUninstallButton_isSystemApp_handleAsDisableableButton() { 236 doReturn(false).when(mController).handleDisableable(); 237 mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; 238 239 mController.updateUninstallButton(); 240 241 verify(mController).handleDisableable(); 242 verify(mButtonPrefs).setButton2Enabled(false); 243 } 244 245 @Test 246 @Config(shadows = ShadowAppUtils.class) isAvailable_nonInstantApp()247 public void isAvailable_nonInstantApp() { 248 mController.mAppEntry = mAppEntry; 249 assertThat(mController.isAvailable()).isTrue(); 250 } 251 252 @Test isAvailable_instantApp()253 public void isAvailable_instantApp() { 254 mController.mAppEntry = mAppEntry; 255 ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", 256 new InstantAppDataProvider() { 257 @Override 258 public boolean isInstantApp(ApplicationInfo info) { 259 return true; 260 } 261 }); 262 assertThat(mController.isAvailable()).isFalse(); 263 } 264 265 @Test updateUninstallButton_isDeviceAdminApp_setButtonDisable()266 public void updateUninstallButton_isDeviceAdminApp_setButtonDisable() { 267 doReturn(true).when(mController).handleDisableable(); 268 mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; 269 doReturn(true).when(mDpm).packageHasActiveAdmins(anyString()); 270 271 mController.updateUninstallButton(); 272 273 verify(mController).handleDisableable(); 274 verify(mButtonPrefs).setButton2Enabled(false); 275 } 276 277 @Test updateUninstallButton_isProfileOrDeviceOwner_setButtonDisable()278 public void updateUninstallButton_isProfileOrDeviceOwner_setButtonDisable() { 279 doReturn(true).when(mDpm).isDeviceOwnerAppOnAnyUser(anyString()); 280 281 mController.updateUninstallButton(); 282 283 verify(mButtonPrefs).setButton2Enabled(false); 284 } 285 286 @Test updateUninstallButton_isDeviceProvisioningApp_setButtonDisable()287 public void updateUninstallButton_isDeviceProvisioningApp_setButtonDisable() { 288 doReturn(true).when(mDpm).isDeviceOwnerAppOnAnyUser(anyString()); 289 when(mSettingsActivity.getResources().getString(anyInt())).thenReturn(PACKAGE_NAME); 290 291 mController.updateUninstallButton(); 292 293 verify(mButtonPrefs).setButton2Enabled(false); 294 } 295 296 @Test updateUninstallButton_isUninstallInQueue_setButtonDisable()297 public void updateUninstallButton_isUninstallInQueue_setButtonDisable() { 298 doReturn(true).when(mDpm).isUninstallInQueue(any()); 299 300 mController.updateUninstallButton(); 301 302 verify(mButtonPrefs).setButton2Enabled(false); 303 } 304 305 @Test updateUninstallButton_isHomeAppAndBundled_setButtonDisable()306 public void updateUninstallButton_isHomeAppAndBundled_setButtonDisable() { 307 mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; 308 mController.mHomePackages.add(PACKAGE_NAME); 309 310 mController.updateUninstallButton(); 311 312 verify(mButtonPrefs).setButton2Enabled(false); 313 } 314 315 @Test updateUninstallButton_isSystemRro_setButtonDisable()316 public void updateUninstallButton_isSystemRro_setButtonDisable() { 317 mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; 318 319 when(mAppInfo.isResourceOverlay()).thenReturn(true); 320 321 mController.updateUninstallButton(); 322 323 verify(mButtonPrefs).setButton2Enabled(false); 324 } 325 326 @Test updateUninstallButton_isNonSystemRro_setButtonDisable()327 public void updateUninstallButton_isNonSystemRro_setButtonDisable() 328 throws RemoteException { 329 when(mAppInfo.isResourceOverlay()).thenReturn(true); 330 when(mOverlayManager.getOverlayInfo(anyString(), any())) 331 .thenReturn(OVERLAY_ENABLED); 332 333 mController.updateUninstallButton(); 334 335 verify(mButtonPrefs).setButton2Enabled(false); 336 } 337 338 @Test updateUninstallButton_isNonSystemRro_setButtonEnable()339 public void updateUninstallButton_isNonSystemRro_setButtonEnable() 340 throws RemoteException { 341 when(mAppInfo.isResourceOverlay()).thenReturn(true); 342 when(mOverlayManager.getOverlayInfo(anyString(), any())) 343 .thenReturn(OVERLAY_DISABLED); 344 345 mController.updateUninstallButton(); 346 347 verify(mButtonPrefs).setButton2Enabled(true); 348 } 349 350 @Test updateForceStopButton_HasActiveAdmins_setButtonDisable()351 public void updateForceStopButton_HasActiveAdmins_setButtonDisable() { 352 doReturn(true).when(mDpm).packageHasActiveAdmins(anyString()); 353 354 mController.updateForceStopButton(); 355 356 verify(mController).updateForceStopButtonInner(false); 357 } 358 359 @Test updateForceStopButton_AppNotStopped_setButtonEnable()360 public void updateForceStopButton_AppNotStopped_setButtonEnable() { 361 mController.updateForceStopButton(); 362 363 verify(mController).updateForceStopButtonInner(true); 364 } 365 366 @Test uninstallPkg_intentSent()367 public void uninstallPkg_intentSent() { 368 mController.uninstallPkg(PACKAGE_NAME, ALL_USERS, DISABLE_AFTER_INSTALL); 369 370 verify(mFragment).startActivityForResult(any(), eq(REQUEST_UNINSTALL)); 371 assertThat( 372 mUninstallIntent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true)) 373 .isEqualTo(ALL_USERS); 374 assertThat(mUninstallIntent.getAction()).isEqualTo(Intent.ACTION_UNINSTALL_PACKAGE); 375 assertThat(mController.mDisableAfterUninstall).isEqualTo(DISABLE_AFTER_INSTALL); 376 } 377 378 @Test forceStopPackage_methodInvokedAndUpdated()379 public void forceStopPackage_methodInvokedAndUpdated() { 380 final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); 381 doReturn(appEntry).when(mState).getEntry(anyString(), anyInt()); 382 doNothing().when(mController).updateForceStopButton(); 383 384 mController.forceStopPackage(PACKAGE_NAME); 385 386 verify(mAm).forceStopPackage(PACKAGE_NAME); 387 assertThat(mController.mAppEntry).isSameAs(appEntry); 388 verify(mController).updateForceStopButton(); 389 } 390 391 @Test handleDisableable_isHomeApp_notControllable()392 public void handleDisableable_isHomeApp_notControllable() { 393 mController.mHomePackages.add(PACKAGE_NAME); 394 395 final boolean controllable = mController.handleDisableable(); 396 397 verify(mButtonPrefs).setButton2Text(R.string.disable_text); 398 assertThat(controllable).isFalse(); 399 } 400 401 @Test handleDisableable_isAppEnabled_controllable()402 public void handleDisableable_isAppEnabled_controllable() { 403 mAppEntry.info.enabled = true; 404 mAppEntry.info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; 405 doReturn(false).when(mController).isSystemPackage(any(), any(), any()); 406 407 final boolean controllable = mController.handleDisableable(); 408 409 verify(mButtonPrefs).setButton2Text(R.string.disable_text); 410 assertThat(controllable).isTrue(); 411 } 412 413 @Test handleDisableable_isAppDisabled_controllable()414 public void handleDisableable_isAppDisabled_controllable() { 415 mAppEntry.info.enabled = false; 416 mAppEntry.info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; 417 doReturn(false).when(mController).isSystemPackage(any(), any(), any()); 418 419 final boolean controllable = mController.handleDisableable(); 420 421 verify(mButtonPrefs).setButton2Text(R.string.enable_text); 422 assertThat(controllable).isTrue(); 423 } 424 425 @Test handleActivityResult_packageUninstalled_shouldFinishPrefernecePanel()426 public void handleActivityResult_packageUninstalled_shouldFinishPrefernecePanel() { 427 doReturn(false).when(mController).refreshUi(); 428 429 mController.handleActivityResult(REQUEST_UNINSTALL, 0, mock(Intent.class)); 430 431 verify(mSettingsActivity).finishPreferencePanel(anyInt(), any(Intent.class)); 432 } 433 434 @Test refreshUi_packageNull_shouldNotCrash()435 public void refreshUi_packageNull_shouldNotCrash() { 436 mController.mPackageName = null; 437 438 // Should not crash in this method 439 assertThat(mController.refreshUi()).isFalse(); 440 } 441 442 @Test onPackageListChanged_available_shouldRefreshUi()443 public void onPackageListChanged_available_shouldRefreshUi() { 444 doReturn(AppButtonsPreferenceController.AVAILABLE) 445 .when(mController).getAvailabilityStatus(); 446 doReturn(true).when(mController).refreshUi(); 447 448 mController.onPackageListChanged(); 449 450 verify(mController).refreshUi(); 451 } 452 453 @Test onPackageListChanged_notAvailable_shouldNotRefreshUiAndNoCrash()454 public void onPackageListChanged_notAvailable_shouldNotRefreshUiAndNoCrash() { 455 doReturn(AppButtonsPreferenceController.DISABLED_FOR_USER) 456 .when(mController).getAvailabilityStatus(); 457 458 mController.onPackageListChanged(); 459 460 verify(mController, never()).refreshUi(); 461 // Should not crash in this method 462 } 463 464 @Test 465 @Config(shadows = ShadowAppUtils.class) getAvailabilityStatus_systemModule()466 public void getAvailabilityStatus_systemModule() { 467 ShadowAppUtils.addHiddenModule(mController.mPackageName); 468 assertThat(mController.getAvailabilityStatus()).isEqualTo( 469 AppButtonsPreferenceController.DISABLED_FOR_USER); 470 } 471 472 /** 473 * The test fragment which implements 474 * {@link ButtonActionDialogFragment.AppButtonsDialogListener} 475 */ 476 public static class TestFragment extends InstrumentedPreferenceFragment 477 implements ButtonActionDialogFragment.AppButtonsDialogListener { 478 479 @Override handleDialogClick(int type)480 public void handleDialogClick(int type) { 481 // Do nothing 482 } 483 484 @Override getMetricsCategory()485 public int getMetricsCategory() { 486 return SettingsEnums.PAGE_UNKNOWN; 487 } 488 } 489 createMock()490 private ActionButtonsPreference createMock() { 491 final ActionButtonsPreference pref = mock(ActionButtonsPreference.class); 492 when(pref.setButton2Text(anyInt())).thenReturn(pref); 493 when(pref.setButton2Icon(anyInt())).thenReturn(pref); 494 when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref); 495 when(pref.setButton2Visible(anyBoolean())).thenReturn(pref); 496 when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); 497 498 return pref; 499 } 500 createFakeOverlay(String pkg, boolean enabled, int priority)501 private static OverlayInfo createFakeOverlay(String pkg, boolean enabled, int priority) { 502 final int state = (enabled) ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED; 503 return new OverlayInfo(pkg /* packageName */, 504 "target.package" /* targetPackageName */, 505 "theme" /* targetOverlayableName */, 506 "category", /* category */ 507 "package", /* baseCodePath */ 508 state, 509 0 /* userId */, 510 priority, 511 false /* isStatic */); 512 } 513 514 @Implements(AppUtils.class) 515 public static class ShadowAppUtils { 516 517 public static Set<String> sSystemModules = new ArraySet<>(); 518 519 @Resetter reset()520 public static void reset() { 521 sSystemModules.clear(); 522 } 523 addHiddenModule(String pkg)524 public static void addHiddenModule(String pkg) { 525 sSystemModules.add(pkg); 526 } 527 528 @Implementation isInstant(ApplicationInfo info)529 protected static boolean isInstant(ApplicationInfo info) { 530 return false; 531 } 532 533 @Implementation isSystemModule(Context context, String packageName)534 protected static boolean isSystemModule(Context context, String packageName) { 535 return sSystemModules.contains(packageName); 536 } 537 } 538 } 539