From 8a41b3f729776b145173a173030c284388c0d7ed Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Fri, 16 Apr 2021 01:39:20 -0700 Subject: [PATCH] Optimizing AppStandby.getIdleUidsForUser This is called by NPMS on boot for all users, and it makes a lot of calls to getAppMinBucket. Optimizing the newly added call into alarm manager internal, and some bookkeeping at the front. Alarm manager now maintains a list of all apps that have requested the permission. This saves any calls into permission manager in hasScheduleExactAlarm. Test: atest FrameworksMockingServicesTests:com.android.server.alarm atest FrameworksServicesTests:AppStandbyControllerTests atrace for boot time analysis. Bug: 185081934 Change-Id: I4f0b80715acb37a777d28f8c41f666b20f26bd8d --- .../server/alarm/AlarmManagerService.java | 63 ++++- .../server/usage/AppStandbyController.java | 78 ++--- .../server/alarm/AlarmManagerServiceTest.java | 266 ++++++++++-------- .../usage/AppStandbyControllerTests.java | 46 +++ 4 files changed, 281 insertions(+), 172 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 03d9a968f7905..e63a7c41ea98e 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -59,7 +59,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.PermissionChecker; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.BatteryManager; @@ -123,6 +123,7 @@ import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; +import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; @@ -198,9 +199,13 @@ public class AlarmManagerService extends SystemService { private UsageStatsManagerInternal mUsageStatsManagerInternal; private ActivityManagerInternal mActivityManagerInternal; private PackageManagerInternal mPackageManagerInternal; + private PermissionManagerServiceInternal mLocalPermissionManager; final Object mLock = new Object(); + /** Immutable set of app ids that have requested SCHEDULE_EXACT_ALARM permission.*/ + @VisibleForTesting + volatile Set mExactAlarmCandidates = Collections.emptySet(); // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray> mPendingBackgroundAlarms = new SparseArray<>(); private long mNextWakeup; @@ -1540,6 +1545,21 @@ public class AlarmManagerService extends SystemService { publishBinderService(Context.ALARM_SERVICE, mService); } + void refreshExactAlarmCandidates() { + final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages( + Manifest.permission.SCHEDULE_EXACT_ALARM); + final Set appIds = new ArraySet<>(candidates.length); + for (final String candidate : candidates) { + final int uid = mPackageManagerInternal.getPackageUid(candidate, + PackageManager.MATCH_ANY_USER, USER_SYSTEM); + if (uid > 0) { + appIds.add(UserHandle.getAppId(uid)); + } + } + // No need to lock. Assignment is always atomic. + mExactAlarmCandidates = Collections.unmodifiableSet(appIds); + } + @Override public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { @@ -1569,6 +1589,11 @@ public class AlarmManagerService extends SystemService { LocalServices.getService(DeviceIdleInternal.class); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + + mLocalPermissionManager = LocalServices.getService( + PermissionManagerServiceInternal.class); + refreshExactAlarmCandidates(); + AppStandbyInternal appStandbyInternal = LocalServices.getService(AppStandbyInternal.class); appStandbyInternal.addListener(new AppStandbyTracker()); @@ -2097,17 +2122,21 @@ public class AlarmManagerService extends SystemService { boolean hasScheduleExactAlarmInternal(String packageName, int uid) { final long start = mStatLogger.getTime(); - // No locking needed as EXACT_ALARM_DENY_LIST is immutable. - final boolean isOnDenyList = mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); - if (isOnDenyList && mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, - packageName) != AppOpsManager.MODE_ALLOWED) { - return false; + final boolean hasPermission; + // No locking needed as all internal containers being queried are immutable. + if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { + hasPermission = false; + } else { + final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, + packageName); + if (mode == AppOpsManager.MODE_DEFAULT) { + hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); + } else { + hasPermission = (mode == AppOpsManager.MODE_ALLOWED); + } } - final boolean has = PermissionChecker.checkPermissionForPreflight(getContext(), - Manifest.permission.SCHEDULE_EXACT_ALARM, -1, uid, packageName) - == PermissionChecker.PERMISSION_GRANTED; mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start); - return has; + return hasPermission; } /** @@ -2489,6 +2518,9 @@ public class AlarmManagerService extends SystemService { pw.print("Num time change events: "); pw.println(mNumTimeChanged); + pw.println(); + pw.println("App ids requesting SCHEDULE_EXACT_ALARM: " + mExactAlarmCandidates); + pw.println(); pw.println("Next alarm clock information: "); pw.increaseIndent(); @@ -3924,6 +3956,7 @@ public class AlarmManagerService extends SystemService { public static final int REMOVE_FOR_CANCELED = 7; public static final int REMOVE_EXACT_ALARMS = 8; public static final int EXACT_ALARM_DENY_LIST_CHANGED = 9; + public static final int REFRESH_EXACT_ALARM_CANDIDATES = 10; AlarmHandler() { super(Looper.myLooper()); @@ -4015,6 +4048,9 @@ public class AlarmManagerService extends SystemService { handlePackagesAddedToExactAlarmsDenyListLocked((ArraySet) msg.obj); } break; + case REFRESH_EXACT_ALARM_CANDIDATES: + refreshExactAlarmCandidates(); + break; default: // nope, just ignore it break; @@ -4135,6 +4171,7 @@ public class AlarmManagerService extends SystemService { public UninstallReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); filter.addDataScheme(IntentFilter.SCHEME_PACKAGE); @@ -4179,8 +4216,11 @@ public class AlarmManagerService extends SystemService { case Intent.ACTION_PACKAGE_REMOVED: if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // This package is being updated; don't kill its alarms. + // We will refresh the exact alarm candidates on subsequent receipt of + // PACKAGE_ADDED. return; } + mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES); // Intentional fall-through. case Intent.ACTION_PACKAGE_RESTARTED: final Uri data = intent.getData(); @@ -4191,6 +4231,9 @@ public class AlarmManagerService extends SystemService { } } break; + case Intent.ACTION_PACKAGE_ADDED: + mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES); + return; } if (pkgList != null && (pkgList.length > 0)) { for (String pkg : pkgList) { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 97ee0e131ef25..ebf4ed0e7cab1 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -58,7 +58,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; -import android.app.AppGlobals; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager.StandbyBuckets; @@ -75,7 +74,6 @@ import android.content.pm.CrossProfileAppsInternal; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; import android.database.ContentObserver; import android.hardware.display.DisplayManager; import android.net.NetworkScoreManager; @@ -101,7 +99,7 @@ import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseIntArray; +import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.view.Display; import android.widget.Toast; @@ -118,6 +116,8 @@ import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.usage.AppIdleHistory.AppUsageHistory; +import libcore.util.EmptyArray; + import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; @@ -1249,71 +1249,55 @@ public class AppStandbyController @Override public int[] getIdleUidsForUser(int userId) { if (!mAppIdleEnabled) { - return new int[0]; + return EmptyArray.INT; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getIdleUidsForUser"); final long elapsedRealtime = mInjector.elapsedRealtime(); - List apps; - try { - ParceledListSlice slice = AppGlobals.getPackageManager() - .getInstalledApplications(/* flags= */ 0, userId); - if (slice == null) { - return new int[0]; - } - apps = slice.getList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); + final List apps = pmi.getInstalledApplications(0, userId, Process.myUid()); + if (apps == null) { + return EmptyArray.INT; } - // State of each uid. Key is the uid. Value lower 16 bits is the number of apps - // associated with that uid, upper 16 bits is the number of those apps that is idle. - SparseIntArray uidStates = new SparseIntArray(); - - // Now resolve all app state. Iterating over all apps, keeping track of how many - // we find for each uid and how many of those are idle. + // State of each uid: Key is the uid, value is whether all the apps in that uid are idle. + final SparseBooleanArray uidIdleStates = new SparseBooleanArray(); + int notIdleCount = 0; for (int i = apps.size() - 1; i >= 0; i--) { - ApplicationInfo ai = apps.get(i); + final ApplicationInfo ai = apps.get(i); + final int index = uidIdleStates.indexOfKey(ai.uid); - // Check whether this app is idle. - boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid), - userId, elapsedRealtime); + final boolean currentIdle = (index < 0) ? true : uidIdleStates.valueAt(index); - int index = uidStates.indexOfKey(ai.uid); + final boolean newIdle = currentIdle && isAppIdleFiltered(ai.packageName, + UserHandle.getAppId(ai.uid), userId, elapsedRealtime); + + if (currentIdle && !newIdle) { + // This transition from true to false can happen at most once per uid in this loop. + notIdleCount++; + } if (index < 0) { - uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0)); + uidIdleStates.put(ai.uid, newIdle); } else { - int value = uidStates.valueAt(index); - uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0)); + uidIdleStates.setValueAt(index, newIdle); } } + int numIdleUids = uidIdleStates.size() - notIdleCount; + final int[] idleUids = new int[numIdleUids]; + for (int i = uidIdleStates.size() - 1; i >= 0; i--) { + if (uidIdleStates.valueAt(i)) { + idleUids[--numIdleUids] = uidIdleStates.keyAt(i); + } + } if (DEBUG) { Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } - int numIdle = 0; - for (int i = uidStates.size() - 1; i >= 0; i--) { - int value = uidStates.valueAt(i); - if ((value&0x7fff) == (value>>16)) { - numIdle++; - } - } - - int[] res = new int[numIdle]; - numIdle = 0; - for (int i = uidStates.size() - 1; i >= 0; i--) { - int value = uidStates.valueAt(i); - if ((value&0x7fff) == (value>>16)) { - res[numIdle] = uidStates.keyAt(i); - numIdle++; - } - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - return res; + return idleUids; } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index d55bbd1d8e45c..683fbd17b78d1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -15,6 +15,7 @@ */ package com.android.server.alarm; +import static android.Manifest.permission.SCHEDULE_EXACT_ALARM; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; @@ -54,6 +55,7 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_CHANGED; +import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA; @@ -99,7 +101,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -114,7 +115,6 @@ import android.app.compat.CompatChanges; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; -import android.content.PermissionChecker; import android.content.pm.PackageManagerInternal; import android.os.BatteryManager; import android.os.Bundle; @@ -139,14 +139,18 @@ import com.android.dx.mockito.inline.extended.MockedVoidMethod; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.server.AlarmManagerInternal; import com.android.server.AppStateTracker; import com.android.server.AppStateTrackerImpl; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.usage.AppStandbyInternal; +import libcore.util.EmptyArray; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -160,6 +164,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -176,6 +181,7 @@ public class AlarmManagerServiceTest { private long mAllowWhileIdleWindow; private AlarmManagerService mService; private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener; + private AlarmManagerService.UninstallReceiver mPackageChangesReceiver; private AlarmManagerService.ChargingReceiver mChargingReceiver; private IAppOpsCallback mIAppOpsCallback; private IAlarmManager mBinder; @@ -190,6 +196,8 @@ public class AlarmManagerServiceTest { @Mock private DeviceIdleInternal mDeviceIdleInternal; @Mock + private PermissionManagerServiceInternal mPermissionManagerInternal; + @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private AppStandbyInternal mAppStandbyInternal; @@ -351,7 +359,6 @@ public class AlarmManagerServiceTest { .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) - .mockStatic(PermissionChecker.class) .mockStatic(Settings.Global.class) .mockStatic(ServiceManager.class) .spyStatic(UserHandle.class) @@ -361,6 +368,8 @@ public class AlarmManagerServiceTest { doReturn(mIActivityManager).when(ActivityManager::getService); doReturn(mDeviceIdleInternal).when( () -> LocalServices.getService(DeviceIdleInternal.class)); + doReturn(mPermissionManagerInternal).when( + () -> LocalServices.getService(PermissionManagerServiceInternal.class)); doReturn(mActivityManagerInternal).when( () -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mPackageManagerInternal).when( @@ -399,8 +408,10 @@ public class AlarmManagerServiceTest { when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); - when(mPackageManagerInternal.getPackageUid(eq(TEST_CALLING_PACKAGE), anyInt(), - eq(TEST_CALLING_USER))).thenReturn(TEST_CALLING_UID); + registerAppIds(new String[]{TEST_CALLING_PACKAGE}, + new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)}); + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING); mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); @@ -424,13 +435,22 @@ public class AlarmManagerServiceTest { verify(mAppStandbyInternal).addListener(captor.capture()); mAppStandbyListener = captor.getValue(); - ArgumentCaptor chargingReceiverCaptor = + final ArgumentCaptor chargingReceiverCaptor = ArgumentCaptor.forClass(AlarmManagerService.ChargingReceiver.class); verify(mMockContext).registerReceiver(chargingReceiverCaptor.capture(), argThat((filter) -> filter.hasAction(BatteryManager.ACTION_CHARGING) && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); mChargingReceiver = chargingReceiverCaptor.getValue(); + final ArgumentCaptor packageReceiverCaptor = + ArgumentCaptor.forClass(AlarmManagerService.UninstallReceiver.class); + verify(mMockContext).registerReceiver(packageReceiverCaptor.capture(), + argThat((filter) -> filter.hasAction(Intent.ACTION_PACKAGE_ADDED) + && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED))); + mPackageChangesReceiver = packageReceiverCaptor.getValue(); + + assertEquals(mService.mExactAlarmCandidates, Collections.emptySet()); + ArgumentCaptor binderCaptor = ArgumentCaptor.forClass(IBinder.class); verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(), anyBoolean(), anyInt())); @@ -927,7 +947,8 @@ public class AlarmManagerServiceTest { private void assertAndHandleMessageSync(int what) { final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture()); + verify(mService.mHandler, atLeastOnce()).sendMessageAtTime(messageCaptor.capture(), + anyLong()); final Message lastMessage = messageCaptor.getValue(); assertEquals("Unexpected message send to handler", lastMessage.what, what); @@ -1795,70 +1816,45 @@ public class AlarmManagerServiceTest { } @Test - public void hasScheduleExactAlarmBinderCallEmptyDenyList() throws RemoteException { - doReturn(PermissionChecker.PERMISSION_GRANTED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + public void hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException { + mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); + assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + + mockExactAlarmPermissionGrant(true, false, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test - public void hasScheduleExactAlarmBinderCallWithDenyList() throws RemoteException { - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); - - when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, - TEST_CALLING_PACKAGE)).thenReturn(MODE_ERRORED); - + public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException { + mockExactAlarmPermissionGrant(true, true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); - - when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, - TEST_CALLING_PACKAGE)).thenReturn(MODE_DEFAULT); + mockExactAlarmPermissionGrant(true, true, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); - - when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, - TEST_CALLING_PACKAGE)).thenReturn(MODE_IGNORED); + mockExactAlarmPermissionGrant(true, true, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); - - when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, - TEST_CALLING_PACKAGE)).thenReturn(MODE_ALLOWED); - - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); - - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); - - doReturn(PermissionChecker.PERMISSION_GRANTED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, true, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), times(2)); + } + + @Test + public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException { + mockExactAlarmPermissionGrant(false, false, MODE_DEFAULT); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + + mockExactAlarmPermissionGrant(false, false, MODE_ALLOWED); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); + + mockExactAlarmPermissionGrant(false, true, MODE_ALLOWED); + assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test @@ -1884,9 +1880,8 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); + verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, + TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); } @@ -1969,10 +1964,7 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - doReturn(PermissionChecker.PERMISSION_GRANTED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); @@ -1980,9 +1972,7 @@ public class AlarmManagerServiceTest { alarmPi, null, null, null, alarmClock); // Correct permission checks are invoked. - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -1996,6 +1986,22 @@ public class AlarmManagerServiceTest { assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } + private void mockExactAlarmPermissionGrant(boolean declared, boolean denyList, int mode) { + String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING; + when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM)) + .thenReturn(requesters); + mService.refreshExactAlarmCandidates(); + + if (denyList) { + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); + } else { + setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); + } + + when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(mode); + } + @Test public void alarmClockBinderCallWithoutPermission() throws RemoteException { setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true); @@ -2003,10 +2009,7 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2018,9 +2021,6 @@ public class AlarmManagerServiceTest { } catch (SecurityException se) { // Expected. } - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); } @@ -2030,14 +2030,12 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - // Permission check is granted by default by the mock. + mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -2057,19 +2055,13 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); // If permission is denied, only then allowlist will be checked. - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), @@ -2084,14 +2076,11 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - // Permission check is granted by default by the mock. + mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -2111,19 +2100,13 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); // If permission is denied, only then allowlist will be checked. - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -2145,10 +2128,7 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2166,9 +2146,6 @@ public class AlarmManagerServiceTest { } catch (SecurityException se) { // Expected. } - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), times(2)); verify(mDeviceIdleInternal, times(2)).isAppOnWhitelist(anyInt()); } @@ -2184,9 +2161,7 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); - verify(() -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE)), never()); + verify(mService, never()).hasScheduleExactAlarmInternal(anyString(), anyInt()); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -2205,10 +2180,7 @@ public class AlarmManagerServiceTest { () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), anyString(), any(UserHandle.class))); - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); @@ -2355,10 +2327,7 @@ public class AlarmManagerServiceTest { @Test public void opScheduleExactAlarmRevoked() throws Exception { - doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( - () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), - eq(Manifest.permission.SCHEDULE_EXACT_ALARM), anyInt(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, @@ -2456,6 +2425,73 @@ public class AlarmManagerServiceTest { assertEquals(LazyAlarmStore.TAG, mService.mAlarmStore.getName()); } + private void registerAppIds(String[] packages, Integer[] ids) { + assertEquals(packages.length, ids.length); + + when(mPackageManagerInternal.getPackageUid(anyString(), anyInt(), anyInt())).thenAnswer( + invocation -> { + final String pkg = invocation.getArgument(0); + final int index = ArrayUtils.indexOf(packages, pkg); + if (index < 0) { + return index; + } + final int userId = invocation.getArgument(2); + return UserHandle.getUid(userId, ids[index]); + }); + } + + @Test + public void refreshExactAlarmCandidatesOnPackageAdded() { + final String[] exactAlarmRequesters = new String[]{"p11", "p2", "p9"}; + final Integer[] appIds = new Integer[]{11, 2, 9}; + registerAppIds(exactAlarmRequesters, appIds); + + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); + + final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) + .setPackage(TEST_CALLING_PACKAGE); + mPackageChangesReceiver.onReceive(mMockContext, packageAdded); + + assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); + assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); + } + + @Test + public void refreshExactAlarmCandidatesOnPackageReplaced() { + final String[] exactAlarmRequesters = new String[]{"p15", "p21", "p3"}; + final Integer[] appIds = new Integer[]{15, 21, 3}; + registerAppIds(exactAlarmRequesters, appIds); + + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); + + final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) + .setPackage(TEST_CALLING_PACKAGE) + .putExtra(Intent.EXTRA_REPLACING, true); + mPackageChangesReceiver.onReceive(mMockContext, packageAdded); + + assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); + assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); + } + + @Test + public void refreshExactAlarmCandidatesOnPackageRemoved() { + final String[] exactAlarmRequesters = new String[]{"p99", "p1", "p19"}; + final Integer[] appIds = new Integer[]{99, 1, 19}; + registerAppIds(exactAlarmRequesters, appIds); + + when(mPermissionManagerInternal.getAppOpPermissionPackages( + SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); + + final Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED) + .setPackage(TEST_CALLING_PACKAGE); + mPackageChangesReceiver.onReceive(mMockContext, packageRemoved); + + assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); + assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 916a278a1567a..a24691791938e 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -59,11 +59,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.mockito.AdditionalMatchers.not; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; @@ -77,6 +80,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; @@ -92,6 +96,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; @@ -101,6 +106,8 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; @@ -194,6 +201,8 @@ public class AppStandbyControllerTests { } static class MyInjector extends AppStandbyController.Injector { + @Mock + private PackageManagerInternal mPackageManagerInternal; long mElapsedRealtime; boolean mIsAppIdleEnabled = true; boolean mIsCharging; @@ -222,6 +231,7 @@ public class AppStandbyControllerTests { MyInjector(Context context, Looper looper) { super(context, looper); + MockitoAnnotations.initMocks(this); } @Override @@ -268,6 +278,11 @@ public class AppStandbyControllerTests { return mClockApps.contains(Pair.create(packageName, uid)); } + @Override + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + @Override void updatePowerWhitelistCache() { } @@ -491,6 +506,37 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime, false)); } + @Test + public void testGetIdleUidsForUser() { + final AppStandbyController controllerUnderTest = spy(mController); + + final int userIdForTest = 325; + final int[] uids = new int[]{129, 23, 129, 129, 44, 23, 41, 751}; + final boolean[] idle = new boolean[]{true, true, false, true, false, true, false, true}; + // Based on uids[] and idle[], the only two uids that have all true's in idle[]. + final int[] expectedIdleUids = new int[]{23, 751}; + + final List installedApps = new ArrayList<>(); + for (int i = 0; i < uids.length; i++) { + final ApplicationInfo ai = mock(ApplicationInfo.class); + ai.uid = uids[i]; + ai.packageName = "example.package.name." + i; + installedApps.add(ai); + when(controllerUnderTest.isAppIdleFiltered(eq(ai.packageName), + eq(UserHandle.getAppId(ai.uid)), eq(userIdForTest), anyLong())) + .thenReturn(idle[i]); + } + when(mInjector.mPackageManagerInternal.getInstalledApplications(anyInt(), eq(userIdForTest), + anyInt())).thenReturn(installedApps); + final int[] returnedIdleUids = controllerUnderTest.getIdleUidsForUser(userIdForTest); + + assertEquals(expectedIdleUids.length, returnedIdleUids.length); + for (final int uid : expectedIdleUids) { + assertTrue("Idle uid: " + uid + " not found in result: " + Arrays.toString( + returnedIdleUids), ArrayUtils.contains(returnedIdleUids, uid)); + } + } + private static class TestParoleListener extends AppIdleStateChangeListener { private boolean mIsParoleOn = false; private CountDownLatch mLatch;