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.server.usage; 18 19 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; 20 import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN; 21 import static android.app.usage.UsageEvents.Event.SLICE_PINNED; 22 import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV; 23 import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; 24 import static android.app.usage.UsageEvents.Event.USER_INTERACTION; 25 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; 26 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; 27 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; 28 import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; 29 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; 30 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; 31 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; 32 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; 33 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; 34 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; 35 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; 36 37 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertFalse; 39 import static org.junit.Assert.assertNotEquals; 40 import static org.junit.Assert.assertTrue; 41 import static org.junit.Assert.fail; 42 import static org.mockito.ArgumentMatchers.eq; 43 import static org.mockito.Matchers.anyInt; 44 import static org.mockito.Mockito.doReturn; 45 import static org.mockito.Mockito.mock; 46 47 import android.app.usage.AppStandbyInfo; 48 import android.app.usage.UsageEvents; 49 import android.app.usage.UsageStatsManagerInternal; 50 import android.appwidget.AppWidgetManager; 51 import android.content.Context; 52 import android.content.ContextWrapper; 53 import android.content.pm.ApplicationInfo; 54 import android.content.pm.PackageInfo; 55 import android.content.pm.PackageManager; 56 import android.hardware.display.DisplayManager; 57 import android.os.Handler; 58 import android.os.Looper; 59 import android.os.RemoteException; 60 import android.platform.test.annotations.Presubmit; 61 import android.util.ArraySet; 62 import android.view.Display; 63 64 import androidx.test.InstrumentationRegistry; 65 import androidx.test.filters.SmallTest; 66 import androidx.test.runner.AndroidJUnit4; 67 68 import com.android.server.SystemService; 69 70 import org.junit.Before; 71 import org.junit.Test; 72 import org.junit.runner.RunWith; 73 74 import java.io.File; 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.List; 78 import java.util.Set; 79 import java.util.concurrent.CountDownLatch; 80 import java.util.concurrent.TimeUnit; 81 82 /** 83 * Unit test for AppStandbyController. 84 */ 85 @RunWith(AndroidJUnit4.class) 86 @Presubmit 87 @SmallTest 88 public class AppStandbyControllerTests { 89 90 private static final String PACKAGE_1 = "com.example.foo"; 91 private static final int UID_1 = 10000; 92 private static final String PACKAGE_EXEMPTED_1 = "com.android.exempted"; 93 private static final int UID_EXEMPTED_1 = 10001; 94 private static final int USER_ID = 0; 95 private static final int USER_ID2 = 10; 96 97 private static final String PACKAGE_UNKNOWN = "com.example.unknown"; 98 99 private static final String ADMIN_PKG = "com.android.admin"; 100 private static final String ADMIN_PKG2 = "com.android.admin2"; 101 private static final String ADMIN_PKG3 = "com.android.admin3"; 102 103 private static final long MINUTE_MS = 60 * 1000; 104 private static final long HOUR_MS = 60 * MINUTE_MS; 105 private static final long DAY_MS = 24 * HOUR_MS; 106 107 private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS; 108 private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS; 109 private static final long RARE_THRESHOLD = 48 * HOUR_MS; 110 // Short STABLE_CHARGING_THRESHOLD for testing purposes 111 private static final long STABLE_CHARGING_THRESHOLD = 2000; 112 113 /** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */ 114 private static boolean isPackageInstalled = true; 115 116 private MyInjector mInjector; 117 private AppStandbyController mController; 118 119 static class MyContextWrapper extends ContextWrapper { 120 PackageManager mockPm = mock(PackageManager.class); 121 MyContextWrapper(Context base)122 public MyContextWrapper(Context base) { 123 super(base); 124 } 125 getPackageManager()126 public PackageManager getPackageManager() { 127 return mockPm; 128 } 129 } 130 131 static class MyInjector extends AppStandbyController.Injector { 132 long mElapsedRealtime; 133 boolean mIsAppIdleEnabled = true; 134 boolean mIsCharging; 135 List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>(); 136 boolean mDisplayOn; 137 DisplayManager.DisplayListener mDisplayListener; 138 String mBoundWidgetPackage = PACKAGE_EXEMPTED_1; 139 MyInjector(Context context, Looper looper)140 MyInjector(Context context, Looper looper) { 141 super(context, looper); 142 } 143 144 @Override onBootPhase(int phase)145 void onBootPhase(int phase) { 146 } 147 148 @Override getBootPhase()149 int getBootPhase() { 150 return SystemService.PHASE_BOOT_COMPLETED; 151 } 152 153 @Override elapsedRealtime()154 long elapsedRealtime() { 155 return mElapsedRealtime; 156 } 157 158 @Override currentTimeMillis()159 long currentTimeMillis() { 160 return mElapsedRealtime; 161 } 162 163 @Override isAppIdleEnabled()164 boolean isAppIdleEnabled() { 165 return mIsAppIdleEnabled; 166 } 167 168 @Override isCharging()169 boolean isCharging() { 170 return mIsCharging; 171 } 172 173 @Override isPowerSaveWhitelistExceptIdleApp(String packageName)174 boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { 175 return mPowerSaveWhitelistExceptIdle.contains(packageName); 176 } 177 178 @Override getDataSystemDirectory()179 File getDataSystemDirectory() { 180 return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal())); 181 } 182 183 @Override noteEvent(int event, String packageName, int uid)184 void noteEvent(int event, String packageName, int uid) throws RemoteException { 185 } 186 187 @Override isPackageEphemeral(int userId, String packageName)188 boolean isPackageEphemeral(int userId, String packageName) { 189 // TODO: update when testing ephemeral apps scenario 190 return false; 191 } 192 193 @Override isPackageInstalled(String packageName, int flags, int userId)194 boolean isPackageInstalled(String packageName, int flags, int userId) { 195 // Should always return true (default value) unless testing for an uninstalled app 196 return isPackageInstalled; 197 } 198 199 @Override getRunningUserIds()200 int[] getRunningUserIds() { 201 return new int[] {USER_ID}; 202 } 203 204 @Override isDefaultDisplayOn()205 boolean isDefaultDisplayOn() { 206 return mDisplayOn; 207 } 208 209 @Override registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler)210 void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { 211 mDisplayListener = listener; 212 } 213 214 @Override getActiveNetworkScorer()215 String getActiveNetworkScorer() { 216 return null; 217 } 218 219 @Override isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName, int userId)220 public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName, 221 int userId) { 222 return packageName != null && packageName.equals(mBoundWidgetPackage); 223 } 224 225 @Override getAppIdleSettings()226 String getAppIdleSettings() { 227 return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/" 228 + WORKING_SET_THRESHOLD + "/" 229 + FREQUENT_THRESHOLD + "/" 230 + RARE_THRESHOLD + "," 231 + "stable_charging_threshold=" + STABLE_CHARGING_THRESHOLD; 232 } 233 234 @Override isDeviceIdleMode()235 public boolean isDeviceIdleMode() { 236 return false; 237 } 238 239 // Internal methods 240 setDisplayOn(boolean on)241 void setDisplayOn(boolean on) { 242 mDisplayOn = on; 243 if (mDisplayListener != null) { 244 mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY); 245 } 246 } 247 } 248 setupPm(PackageManager mockPm)249 private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException { 250 List<PackageInfo> packages = new ArrayList<>(); 251 PackageInfo pi = new PackageInfo(); 252 pi.applicationInfo = new ApplicationInfo(); 253 pi.applicationInfo.uid = UID_1; 254 pi.packageName = PACKAGE_1; 255 packages.add(pi); 256 257 PackageInfo pie = new PackageInfo(); 258 pie.applicationInfo = new ApplicationInfo(); 259 pie.applicationInfo.uid = UID_EXEMPTED_1; 260 pie.packageName = PACKAGE_EXEMPTED_1; 261 packages.add(pie); 262 263 doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt()); 264 try { 265 doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt(), anyInt()); 266 doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1), 267 anyInt(), anyInt()); 268 doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(eq(pi.packageName), 269 anyInt()); 270 doReturn(pie.applicationInfo).when(mockPm).getApplicationInfo(eq(pie.packageName), 271 anyInt()); 272 } catch (PackageManager.NameNotFoundException nnfe) {} 273 } 274 setChargingState(AppStandbyController controller, boolean charging)275 private void setChargingState(AppStandbyController controller, boolean charging) { 276 mInjector.mIsCharging = charging; 277 if (controller != null) { 278 controller.setChargingState(charging); 279 } 280 } 281 setAppIdleEnabled(AppStandbyController controller, boolean enabled)282 private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) { 283 mInjector.mIsAppIdleEnabled = enabled; 284 if (controller != null) { 285 controller.setAppIdleEnabled(enabled); 286 } 287 } 288 setupController()289 private AppStandbyController setupController() throws Exception { 290 mInjector.mElapsedRealtime = 0; 291 setupPm(mInjector.getContext().getPackageManager()); 292 AppStandbyController controller = new AppStandbyController(mInjector); 293 controller.initializeDefaultsForSystemApps(USER_ID); 294 controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); 295 controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); 296 mInjector.setDisplayOn(false); 297 mInjector.setDisplayOn(true); 298 setChargingState(controller, false); 299 controller.checkIdleStates(USER_ID); 300 assertEquals(STANDBY_BUCKET_EXEMPTED, 301 controller.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID, 302 mInjector.mElapsedRealtime, false)); 303 assertNotEquals(STANDBY_BUCKET_EXEMPTED, 304 controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 305 mInjector.mElapsedRealtime, false)); 306 307 return controller; 308 } 309 getCurrentTime()310 private long getCurrentTime() { 311 return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 312 } 313 314 @Before setUp()315 public void setUp() throws Exception { 316 MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext()); 317 mInjector = new MyInjector(myContext, Looper.getMainLooper()); 318 mController = setupController(); 319 setChargingState(mController, false); 320 } 321 322 private class TestParoleListener extends UsageStatsManagerInternal.AppIdleStateChangeListener { 323 private boolean mOnParole = false; 324 private CountDownLatch mLatch; 325 private long mLastParoleChangeTime; 326 private boolean mIsExpecting = false; 327 private boolean mExpectedParoleState; 328 getParoleState()329 public boolean getParoleState() { 330 synchronized (this) { 331 return mOnParole; 332 } 333 } 334 rearmLatch()335 public void rearmLatch() { 336 synchronized (this) { 337 mLatch = new CountDownLatch(1); 338 mIsExpecting = false; 339 } 340 } 341 rearmLatch(boolean expectedParoleState)342 public void rearmLatch(boolean expectedParoleState) { 343 synchronized (this) { 344 mLatch = new CountDownLatch(1); 345 mIsExpecting = true; 346 mExpectedParoleState = expectedParoleState; 347 } 348 } 349 awaitOnLatch(long time)350 public void awaitOnLatch(long time) throws Exception { 351 mLatch.await(time, TimeUnit.MILLISECONDS); 352 } 353 getLastParoleChangeTime()354 public long getLastParoleChangeTime() { 355 synchronized (this) { 356 return mLastParoleChangeTime; 357 } 358 } 359 360 @Override onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, int reason)361 public void onAppIdleStateChanged(String packageName, int userId, boolean idle, 362 int bucket, int reason) { 363 } 364 365 @Override onParoleStateChanged(boolean isParoleOn)366 public void onParoleStateChanged(boolean isParoleOn) { 367 synchronized (this) { 368 // Only record information if it is being looked for 369 if (mLatch != null && mLatch.getCount() > 0) { 370 mOnParole = isParoleOn; 371 mLastParoleChangeTime = getCurrentTime(); 372 if (!mIsExpecting || isParoleOn == mExpectedParoleState) { 373 mLatch.countDown(); 374 } 375 } 376 } 377 } 378 } 379 380 @Test testCharging()381 public void testCharging() throws Exception { 382 long startTime; 383 TestParoleListener paroleListener = new TestParoleListener(); 384 long marginOfError = 200; 385 386 // Charging 387 paroleListener.rearmLatch(); 388 mController.addListener(paroleListener); 389 startTime = getCurrentTime(); 390 setChargingState(mController, true); 391 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 392 assertTrue(paroleListener.mOnParole); 393 // Parole will only be granted after device has been charging for a sufficient amount of 394 // time. 395 assertEquals(STABLE_CHARGING_THRESHOLD, 396 paroleListener.getLastParoleChangeTime() - startTime, 397 marginOfError); 398 399 // Discharging 400 paroleListener.rearmLatch(); 401 startTime = getCurrentTime(); 402 setChargingState(mController, false); 403 mController.checkIdleStates(USER_ID); 404 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 405 assertFalse(paroleListener.getParoleState()); 406 // Parole should be revoked immediately 407 assertEquals(0, 408 paroleListener.getLastParoleChangeTime() - startTime, 409 marginOfError); 410 411 // Brief Charging 412 paroleListener.rearmLatch(); 413 setChargingState(mController, true); 414 setChargingState(mController, false); 415 // Device stopped charging before the stable charging threshold. 416 // Parole should not be granted at the end of the threshold 417 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 418 assertFalse(paroleListener.getParoleState()); 419 420 // Charging Again 421 paroleListener.rearmLatch(); 422 startTime = getCurrentTime(); 423 setChargingState(mController, true); 424 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 425 assertTrue(paroleListener.getParoleState()); 426 assertTrue(paroleListener.mOnParole); 427 assertEquals(STABLE_CHARGING_THRESHOLD, 428 paroleListener.getLastParoleChangeTime() - startTime, 429 marginOfError); 430 } 431 432 @Test testEnabledState()433 public void testEnabledState() throws Exception { 434 TestParoleListener paroleListener = new TestParoleListener(); 435 paroleListener.rearmLatch(true); 436 mController.addListener(paroleListener); 437 long lastUpdateTime; 438 439 // Test that listeners are notified if enabled changes when the device is not in parole. 440 setChargingState(mController, false); 441 442 // Start off not enabled. Device is effectively in permanent parole. 443 setAppIdleEnabled(mController, false); 444 // Since AppStandbyController uses a handler to notify listeners of a state change, there is 445 // some inherent latency between changing the state and getting the notification. We need to 446 // wait until the paroleListener has been notified that parole is on before continuing with 447 // the test. 448 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 449 assertTrue(paroleListener.mOnParole); 450 451 // Enable controller 452 paroleListener.rearmLatch(); 453 setAppIdleEnabled(mController, true); 454 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 455 assertFalse(paroleListener.mOnParole); 456 lastUpdateTime = paroleListener.getLastParoleChangeTime(); 457 458 paroleListener.rearmLatch(); 459 setAppIdleEnabled(mController, true); 460 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 461 assertFalse(paroleListener.mOnParole); 462 // Make sure AppStandbyController doesn't notify listeners when there's no change. 463 assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); 464 465 // Disable controller 466 paroleListener.rearmLatch(); 467 setAppIdleEnabled(mController, false); 468 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 469 assertTrue(paroleListener.mOnParole); 470 lastUpdateTime = paroleListener.getLastParoleChangeTime(); 471 472 paroleListener.rearmLatch(); 473 setAppIdleEnabled(mController, false); 474 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 475 assertTrue(paroleListener.mOnParole); 476 // Make sure AppStandbyController doesn't notify listeners when there's no change. 477 assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); 478 479 480 // Test that listeners aren't notified if enabled status changes when the device is already 481 // in parole. 482 483 // A device is in parole whenever it's charging. 484 setChargingState(mController, true); 485 486 // Start off not enabled. 487 paroleListener.rearmLatch(); 488 setAppIdleEnabled(mController, false); 489 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 490 assertTrue(paroleListener.mOnParole); 491 lastUpdateTime = paroleListener.getLastParoleChangeTime(); 492 493 // Test that toggling doesn't notify the listener. 494 paroleListener.rearmLatch(); 495 setAppIdleEnabled(mController, true); 496 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 497 assertTrue(paroleListener.mOnParole); 498 assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); 499 500 paroleListener.rearmLatch(); 501 setAppIdleEnabled(mController, false); 502 paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); 503 assertTrue(paroleListener.mOnParole); 504 assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); 505 } 506 assertTimeout(AppStandbyController controller, long elapsedTime, int bucket)507 private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { 508 mInjector.mElapsedRealtime = elapsedTime; 509 controller.checkIdleStates(USER_ID); 510 assertEquals(bucket, 511 controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime, 512 false)); 513 } 514 reportEvent(AppStandbyController controller, int eventType, long elapsedTime, String packageName)515 private void reportEvent(AppStandbyController controller, int eventType, long elapsedTime, 516 String packageName) { 517 // Back to ACTIVE on event 518 mInjector.mElapsedRealtime = elapsedTime; 519 UsageEvents.Event ev = new UsageEvents.Event(); 520 ev.mPackage = packageName; 521 ev.mEventType = eventType; 522 controller.reportEvent(ev, elapsedTime, USER_ID); 523 } 524 getStandbyBucket(AppStandbyController controller, String packageName)525 private int getStandbyBucket(AppStandbyController controller, String packageName) { 526 return controller.getAppStandbyBucket(packageName, USER_ID, mInjector.mElapsedRealtime, 527 true); 528 } 529 assertBucket(int bucket)530 private void assertBucket(int bucket) { 531 assertEquals(bucket, getStandbyBucket(mController, PACKAGE_1)); 532 } 533 534 @Test testBuckets()535 public void testBuckets() throws Exception { 536 assertTimeout(mController, 0, STANDBY_BUCKET_NEVER); 537 538 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 539 540 // ACTIVE bucket 541 assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE); 542 543 // WORKING_SET bucket 544 assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET); 545 546 // WORKING_SET bucket 547 assertTimeout(mController, FREQUENT_THRESHOLD - 1, STANDBY_BUCKET_WORKING_SET); 548 549 // FREQUENT bucket 550 assertTimeout(mController, FREQUENT_THRESHOLD + 1, STANDBY_BUCKET_FREQUENT); 551 552 // RARE bucket 553 assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_RARE); 554 555 reportEvent(mController, USER_INTERACTION, RARE_THRESHOLD + 1, PACKAGE_1); 556 557 assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_ACTIVE); 558 559 // RARE bucket 560 assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE); 561 } 562 563 @Test testSetAppStandbyBucket()564 public void testSetAppStandbyBucket() throws Exception { 565 // For a known package, standby bucket should be set properly 566 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 567 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, 568 REASON_MAIN_TIMEOUT, HOUR_MS); 569 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 570 571 // For an unknown package, standby bucket should not be set, hence NEVER is returned 572 // Ensure the unknown package is not already in history by removing it 573 mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); 574 isPackageInstalled = false; // Mock package is not installed 575 mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE, 576 REASON_MAIN_TIMEOUT, HOUR_MS); 577 isPackageInstalled = true; // Reset mocked variable for other tests 578 assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); 579 } 580 581 @Test testAppStandbyBucketOnInstallAndUninstall()582 public void testAppStandbyBucketOnInstallAndUninstall() throws Exception { 583 // On package install, standby bucket should be ACTIVE 584 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN); 585 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_UNKNOWN)); 586 587 // On uninstall, package should not exist in history and should return a NEVER bucket 588 mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); 589 assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); 590 // Ensure uninstalled app is not in history 591 List<AppStandbyInfo> buckets = mController.getAppStandbyBuckets(USER_ID); 592 for(AppStandbyInfo bucket : buckets) { 593 if (bucket.mPackageName.equals(PACKAGE_UNKNOWN)) { 594 fail("packageName found in app idle history after uninstall."); 595 } 596 } 597 } 598 599 @Test testScreenTimeAndBuckets()600 public void testScreenTimeAndBuckets() throws Exception { 601 mInjector.setDisplayOn(false); 602 603 assertTimeout(mController, 0, STANDBY_BUCKET_NEVER); 604 605 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 606 607 // ACTIVE bucket 608 assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE); 609 610 // WORKING_SET bucket 611 assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET); 612 613 // RARE bucket, should fail because the screen wasn't ON. 614 mInjector.mElapsedRealtime = RARE_THRESHOLD + 1; 615 mController.checkIdleStates(USER_ID); 616 assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); 617 618 mInjector.setDisplayOn(true); 619 assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE); 620 } 621 622 @Test testForcedIdle()623 public void testForcedIdle() throws Exception { 624 mController.forceIdleState(PACKAGE_1, USER_ID, true); 625 assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); 626 assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); 627 628 mController.forceIdleState(PACKAGE_1, USER_ID, false); 629 assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0, 630 true)); 631 assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); 632 } 633 634 @Test testNotificationEvent()635 public void testNotificationEvent() throws Exception { 636 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 637 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 638 mInjector.mElapsedRealtime = 1; 639 reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); 640 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 641 642 mController.forceIdleState(PACKAGE_1, USER_ID, true); 643 reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); 644 assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); 645 } 646 647 @Test testSlicePinnedEvent()648 public void testSlicePinnedEvent() throws Exception { 649 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 650 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 651 mInjector.mElapsedRealtime = 1; 652 reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1); 653 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 654 655 mController.forceIdleState(PACKAGE_1, USER_ID, true); 656 reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1); 657 assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); 658 } 659 660 @Test testSlicePinnedPrivEvent()661 public void testSlicePinnedPrivEvent() throws Exception { 662 mController.forceIdleState(PACKAGE_1, USER_ID, true); 663 reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1); 664 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 665 } 666 667 @Test testPredictionTimedout()668 public void testPredictionTimedout() throws Exception { 669 // Set it to timeout or usage, so that prediction can override it 670 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, 671 REASON_MAIN_TIMEOUT, HOUR_MS); 672 assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); 673 674 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, 675 REASON_MAIN_PREDICTED, HOUR_MS); 676 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 677 678 // Fast forward 12 hours 679 mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD; 680 mController.checkIdleStates(USER_ID); 681 // Should still be in predicted bucket, since prediction timeout is 1 day since prediction 682 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 683 // Fast forward two more hours 684 mInjector.mElapsedRealtime += 2 * HOUR_MS; 685 mController.checkIdleStates(USER_ID); 686 // Should have now applied prediction timeout 687 assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); 688 689 // Fast forward RARE bucket 690 mInjector.mElapsedRealtime += RARE_THRESHOLD; 691 mController.checkIdleStates(USER_ID); 692 // Should continue to apply prediction timeout 693 assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); 694 } 695 696 @Test testOverrides()697 public void testOverrides() throws Exception { 698 // Can force to NEVER 699 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, 700 REASON_MAIN_FORCED, 1 * HOUR_MS); 701 assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); 702 703 // Prediction can't override FORCED reason 704 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 705 REASON_MAIN_FORCED, 1 * HOUR_MS); 706 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, 707 REASON_MAIN_PREDICTED, 1 * HOUR_MS); 708 assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); 709 710 // Prediction can't override NEVER 711 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, 712 REASON_MAIN_DEFAULT, 2 * HOUR_MS); 713 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, 714 REASON_MAIN_PREDICTED, 2 * HOUR_MS); 715 assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); 716 717 // Prediction can't set to NEVER 718 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, 719 REASON_MAIN_USAGE, 2 * HOUR_MS); 720 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, 721 REASON_MAIN_PREDICTED, 2 * HOUR_MS); 722 assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); 723 } 724 725 @Test testTimeout()726 public void testTimeout() throws Exception { 727 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 728 assertBucket(STANDBY_BUCKET_ACTIVE); 729 730 mInjector.mElapsedRealtime = 2000; 731 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 732 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 733 assertBucket(STANDBY_BUCKET_ACTIVE); 734 735 // bucketing works after timeout 736 mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100; 737 mController.checkIdleStates(USER_ID); 738 // Use recent prediction 739 assertBucket(STANDBY_BUCKET_FREQUENT); 740 741 // Way past prediction timeout, use system thresholds 742 mInjector.mElapsedRealtime = RARE_THRESHOLD * 4; 743 mController.checkIdleStates(USER_ID); 744 assertBucket(STANDBY_BUCKET_RARE); 745 } 746 747 @Test testCascadingTimeouts()748 public void testCascadingTimeouts() throws Exception { 749 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 750 assertBucket(STANDBY_BUCKET_ACTIVE); 751 752 reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1); 753 assertBucket(STANDBY_BUCKET_ACTIVE); 754 755 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, 756 REASON_MAIN_PREDICTED, 1000); 757 assertBucket(STANDBY_BUCKET_ACTIVE); 758 759 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 760 REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); 761 assertBucket(STANDBY_BUCKET_WORKING_SET); 762 763 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 764 REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); 765 assertBucket(STANDBY_BUCKET_FREQUENT); 766 } 767 768 @Test testOverlappingTimeouts()769 public void testOverlappingTimeouts() throws Exception { 770 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 771 assertBucket(STANDBY_BUCKET_ACTIVE); 772 773 reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1); 774 assertBucket(STANDBY_BUCKET_ACTIVE); 775 776 // Overlapping USER_INTERACTION before previous one times out 777 reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000, 778 PACKAGE_1); 779 assertBucket(STANDBY_BUCKET_ACTIVE); 780 781 // Still in ACTIVE after first USER_INTERACTION times out 782 mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; 783 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 784 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 785 assertBucket(STANDBY_BUCKET_ACTIVE); 786 787 // Both timed out, so NOTIFICATION_SEEN timeout should be effective 788 mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; 789 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 790 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 791 assertBucket(STANDBY_BUCKET_WORKING_SET); 792 793 mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; 794 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, 795 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 796 assertBucket(STANDBY_BUCKET_RARE); 797 } 798 799 @Test testSystemInteractionTimeout()800 public void testSystemInteractionTimeout() throws Exception { 801 setChargingState(mController, false); 802 803 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 804 // Fast forward to RARE 805 mInjector.mElapsedRealtime = RARE_THRESHOLD + 100; 806 mController.checkIdleStates(USER_ID); 807 assertBucket(STANDBY_BUCKET_RARE); 808 809 // Trigger a SYSTEM_INTERACTION and verify bucket 810 reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); 811 assertBucket(STANDBY_BUCKET_ACTIVE); 812 813 // Verify it's still in ACTIVE close to end of timeout 814 mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100; 815 mController.checkIdleStates(USER_ID); 816 assertBucket(STANDBY_BUCKET_ACTIVE); 817 818 // Verify bucket moves to RARE after timeout 819 mInjector.mElapsedRealtime += 200; 820 mController.checkIdleStates(USER_ID); 821 assertBucket(STANDBY_BUCKET_RARE); 822 } 823 824 @Test testInitialForegroundServiceTimeout()825 public void testInitialForegroundServiceTimeout() throws Exception { 826 setChargingState(mController, false); 827 828 mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; 829 // Make sure app is in NEVER bucket 830 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, 831 REASON_MAIN_FORCED, mInjector.mElapsedRealtime); 832 mController.checkIdleStates(USER_ID); 833 assertBucket(STANDBY_BUCKET_NEVER); 834 835 mInjector.mElapsedRealtime += 100; 836 837 // Trigger a FOREGROUND_SERVICE_START and verify bucket 838 reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); 839 mController.checkIdleStates(USER_ID); 840 assertBucket(STANDBY_BUCKET_ACTIVE); 841 842 // Verify it's still in ACTIVE close to end of timeout 843 mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100; 844 mController.checkIdleStates(USER_ID); 845 assertBucket(STANDBY_BUCKET_ACTIVE); 846 847 // Verify bucket moves to RARE after timeout 848 mInjector.mElapsedRealtime += 200; 849 mController.checkIdleStates(USER_ID); 850 assertBucket(STANDBY_BUCKET_RARE); 851 852 // Trigger a FOREGROUND_SERVICE_START again 853 reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); 854 mController.checkIdleStates(USER_ID); 855 // Bucket should not be immediately elevated on subsequent service starts 856 assertBucket(STANDBY_BUCKET_RARE); 857 } 858 859 @Test testPredictionNotOverridden()860 public void testPredictionNotOverridden() throws Exception { 861 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 862 assertBucket(STANDBY_BUCKET_ACTIVE); 863 864 mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000; 865 reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); 866 assertBucket(STANDBY_BUCKET_ACTIVE); 867 868 // Falls back to WORKING_SET 869 mInjector.mElapsedRealtime += 5000; 870 mController.checkIdleStates(USER_ID); 871 assertBucket(STANDBY_BUCKET_WORKING_SET); 872 873 // Predict to ACTIVE 874 mInjector.mElapsedRealtime += 1000; 875 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, 876 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 877 assertBucket(STANDBY_BUCKET_ACTIVE); 878 879 // CheckIdleStates should not change the prediction 880 mInjector.mElapsedRealtime += 1000; 881 mController.checkIdleStates(USER_ID); 882 assertBucket(STANDBY_BUCKET_ACTIVE); 883 } 884 885 @Test testPredictionStrikesBack()886 public void testPredictionStrikesBack() throws Exception { 887 reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); 888 assertBucket(STANDBY_BUCKET_ACTIVE); 889 890 // Predict to FREQUENT 891 mInjector.mElapsedRealtime = RARE_THRESHOLD; 892 mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, 893 REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); 894 assertBucket(STANDBY_BUCKET_FREQUENT); 895 896 // Add a short timeout event 897 mInjector.mElapsedRealtime += 1000; 898 reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); 899 assertBucket(STANDBY_BUCKET_ACTIVE); 900 mInjector.mElapsedRealtime += 1000; 901 mController.checkIdleStates(USER_ID); 902 assertBucket(STANDBY_BUCKET_ACTIVE); 903 904 // Verify it reverted to predicted 905 mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2; 906 mController.checkIdleStates(USER_ID); 907 assertBucket(STANDBY_BUCKET_FREQUENT); 908 } 909 910 @Test testAddActiveDeviceAdmin()911 public void testAddActiveDeviceAdmin() { 912 assertActiveAdmins(USER_ID, (String[]) null); 913 assertActiveAdmins(USER_ID2, (String[]) null); 914 915 mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); 916 assertActiveAdmins(USER_ID, ADMIN_PKG); 917 assertActiveAdmins(USER_ID2, (String[]) null); 918 919 mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); 920 assertActiveAdmins(USER_ID, ADMIN_PKG); 921 assertActiveAdmins(USER_ID2, (String[]) null); 922 923 mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); 924 assertActiveAdmins(USER_ID, ADMIN_PKG); 925 assertActiveAdmins(USER_ID2, ADMIN_PKG2); 926 } 927 928 @Test testSetActiveAdminApps()929 public void testSetActiveAdminApps() { 930 assertActiveAdmins(USER_ID, (String[]) null); 931 assertActiveAdmins(USER_ID2, (String[]) null); 932 933 setActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); 934 assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); 935 assertActiveAdmins(USER_ID2, (String[]) null); 936 937 mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); 938 setActiveAdmins(USER_ID2, ADMIN_PKG); 939 assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); 940 assertActiveAdmins(USER_ID2, ADMIN_PKG); 941 942 mController.setActiveAdminApps(null, USER_ID); 943 assertActiveAdmins(USER_ID, (String[]) null); 944 } 945 946 @Test isActiveDeviceAdmin()947 public void isActiveDeviceAdmin() { 948 assertActiveAdmins(USER_ID, (String[]) null); 949 assertActiveAdmins(USER_ID2, (String[]) null); 950 951 mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); 952 assertIsActiveAdmin(ADMIN_PKG, USER_ID); 953 assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2); 954 955 mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); 956 mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID2); 957 assertIsActiveAdmin(ADMIN_PKG, USER_ID); 958 assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); 959 assertIsActiveAdmin(ADMIN_PKG, USER_ID2); 960 assertIsActiveAdmin(ADMIN_PKG2, USER_ID2); 961 962 setActiveAdmins(USER_ID2, ADMIN_PKG2); 963 assertIsActiveAdmin(ADMIN_PKG2, USER_ID2); 964 assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2); 965 assertIsActiveAdmin(ADMIN_PKG, USER_ID); 966 assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); 967 } 968 getAdminAppsStr(int userId)969 private String getAdminAppsStr(int userId) { 970 return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId)); 971 } 972 getAdminAppsStr(int userId, Set<String> adminApps)973 private String getAdminAppsStr(int userId, Set<String> adminApps) { 974 return "admin apps for u" + userId + ": " 975 + (adminApps == null ? "null" : Arrays.toString(adminApps.toArray())); 976 } 977 assertIsActiveAdmin(String adminApp, int userId)978 private void assertIsActiveAdmin(String adminApp, int userId) { 979 assertTrue(adminApp + " should be an active admin; " + getAdminAppsStr(userId), 980 mController.isActiveDeviceAdmin(adminApp, userId)); 981 } 982 assertIsNotActiveAdmin(String adminApp, int userId)983 private void assertIsNotActiveAdmin(String adminApp, int userId) { 984 assertFalse(adminApp + " shouldn't be an active admin; " + getAdminAppsStr(userId), 985 mController.isActiveDeviceAdmin(adminApp, userId)); 986 } 987 assertActiveAdmins(int userId, String... admins)988 private void assertActiveAdmins(int userId, String... admins) { 989 final Set<String> actualAdminApps = mController.getActiveAdminAppsForTest(userId); 990 if (admins == null) { 991 if (actualAdminApps != null && !actualAdminApps.isEmpty()) { 992 fail("Admin apps should be null; " + getAdminAppsStr(userId, actualAdminApps)); 993 } 994 return; 995 } 996 assertEquals("No. of admin apps not equal; " + getAdminAppsStr(userId, actualAdminApps) 997 + "; expected=" + Arrays.toString(admins), admins.length, actualAdminApps.size()); 998 final Set<String> adminAppsCopy = new ArraySet<>(actualAdminApps); 999 for (String admin : admins) { 1000 adminAppsCopy.remove(admin); 1001 } 1002 assertTrue("Unexpected admin apps; " + getAdminAppsStr(userId, actualAdminApps) 1003 + "; expected=" + Arrays.toString(admins), adminAppsCopy.isEmpty()); 1004 } 1005 setActiveAdmins(int userId, String... admins)1006 private void setActiveAdmins(int userId, String... admins) { 1007 mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId); 1008 } 1009 } 1010