From 0acaadf0ef714ee0b6aba7fd6a0ff2dceacaf3c1 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 21 Feb 2024 14:09:55 +0000 Subject: Exempt carrier privileged apps from flex policy. Carrier privileged apps can do work that's important for proper connection and functionality. Therefore, exempt their DEFAULT+ jobs from flex policy so they're not unintentionally delayed significantly. Bug: 236261941 Test: atest CtsJobSchedulerTestCases:FlexibilityConstraintTest Test: atest FrameworksMockingServicesTests:FlexibilityControllerTest Change-Id: Ic1dfb2b1694f5f33b2200c4af5d3d4c3dfb9e66e --- .../job/controllers/FlexibilityController.java | 141 ++++++++++++ .../job/controllers/FlexibilityControllerTest.java | 237 ++++++++++++++++++++- 2 files changed, 373 insertions(+), 5 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index e96d07f44b34..ee9400fb8408 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -46,8 +46,11 @@ import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.telephony.TelephonyManager; +import android.telephony.UiccSlotMapping; import android.util.ArraySet; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -68,6 +71,8 @@ import com.android.server.utils.AlarmQueue; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Set; import java.util.function.Predicate; /** @@ -1620,9 +1625,21 @@ public final class FlexibilityController extends StateController { private final Object mSatLock = new Object(); private DeviceIdleInternal mDeviceIdleInternal; + private TelephonyManager mTelephonyManager; + + private final boolean mHasFeatureTelephonySubscription; /** Set of all apps that have been deemed special, keyed by user ID. */ private final SparseSetArray mSpecialApps = new SparseSetArray<>(); + /** + * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged + * for. + */ + @GuardedBy("mSatLock") + private final SparseSetArray mCarrierPrivilegedApps = new SparseSetArray<>(); + @GuardedBy("mSatLock") + private final SparseArray + mCarrierPrivilegedCallbacks = new SparseArray<>(); @GuardedBy("mSatLock") private final ArraySet mPowerAllowlistedApps = new ArraySet<>(); @@ -1630,6 +1647,10 @@ public final class FlexibilityController extends StateController { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { + case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED: + updateCarrierPrivilegedCallbackRegistration(); + break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); break; @@ -1637,6 +1658,11 @@ public final class FlexibilityController extends StateController { } }; + SpecialAppTracker() { + mHasFeatureTelephonySubscription = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION); + } + public boolean isSpecialApp(final int userId, @NonNull String packageName) { synchronized (mSatLock) { if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) { @@ -1654,6 +1680,12 @@ public final class FlexibilityController extends StateController { if (mPowerAllowlistedApps.contains(packageName)) { return true; } + for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) { + if (mCarrierPrivilegedApps.contains( + mCarrierPrivilegedApps.keyAt(l), packageName)) { + return true; + } + } } return false; } @@ -1669,9 +1701,12 @@ public final class FlexibilityController extends StateController { private void onSystemServicesReady() { mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); synchronized (mLock) { if (mFlexibilityEnabled) { + mHandler.post( + SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration); mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); } } @@ -1686,6 +1721,13 @@ public final class FlexibilityController extends StateController { private void startTracking() { IntentFilter filter = new IntentFilter( PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); + + if (mHasFeatureTelephonySubscription) { + filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + + updateCarrierPrivilegedCallbackRegistration(); + } + mContext.registerReceiver(mBroadcastReceiver, filter); updatePowerAllowlistCache(); @@ -1695,9 +1737,61 @@ public final class FlexibilityController extends StateController { mContext.unregisterReceiver(mBroadcastReceiver); synchronized (mSatLock) { + mCarrierPrivilegedApps.clear(); mPowerAllowlistedApps.clear(); mSpecialApps.clear(); + + for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { + mTelephonyManager.unregisterCarrierPrivilegesCallback( + mCarrierPrivilegedCallbacks.valueAt(i)); + } + mCarrierPrivilegedCallbacks.clear(); + } + } + + private void updateCarrierPrivilegedCallbackRegistration() { + if (mTelephonyManager == null) { + return; + } + if (!mHasFeatureTelephonySubscription) { + return; + } + + Collection simSlotMapping = mTelephonyManager.getSimSlotMapping(); + final ArraySet changedPkgs = new ArraySet<>(); + synchronized (mSatLock) { + final IntArray callbacksToRemove = new IntArray(); + for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { + callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i)); + } + for (UiccSlotMapping mapping : simSlotMapping) { + final int logicalIndex = mapping.getLogicalSlotIndex(); + if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) { + // Callback already exists. No need to create a new one or remove it. + callbacksToRemove.remove(logicalIndex); + continue; + } + final LogicalIndexCarrierPrivilegesCallback callback = + new LogicalIndexCarrierPrivilegesCallback(logicalIndex); + mCarrierPrivilegedCallbacks.put(logicalIndex, callback); + // Upon registration, the callbacks will be called with the current list of + // apps, so there's no need to query the app list synchronously. + mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex, + AppSchedulingModuleThread.getExecutor(), callback); + } + + for (int i = callbacksToRemove.size() - 1; i >= 0; --i) { + final int logicalIndex = callbacksToRemove.get(i); + final LogicalIndexCarrierPrivilegesCallback callback = + mCarrierPrivilegedCallbacks.get(logicalIndex); + mTelephonyManager.unregisterCarrierPrivilegesCallback(callback); + mCarrierPrivilegedCallbacks.remove(logicalIndex); + changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex)); + mCarrierPrivilegedApps.remove(logicalIndex); + } } + + updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); } /** @@ -1762,17 +1856,64 @@ public final class FlexibilityController extends StateController { updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); } + class LogicalIndexCarrierPrivilegesCallback implements + TelephonyManager.CarrierPrivilegesCallback { + public final int logicalIndex; + + LogicalIndexCarrierPrivilegesCallback(int logicalIndex) { + this.logicalIndex = logicalIndex; + } + + @Override + public void onCarrierPrivilegesChanged(@NonNull Set privilegedPackageNames, + @NonNull Set privilegedUids) { + final ArraySet changedPkgs = new ArraySet<>(); + synchronized (mSatLock) { + final ArraySet oldPrivilegedSet = + mCarrierPrivilegedApps.get(logicalIndex); + if (oldPrivilegedSet != null) { + changedPkgs.addAll(oldPrivilegedSet); + mCarrierPrivilegedApps.remove(logicalIndex); + } + for (String pkgName : privilegedPackageNames) { + mCarrierPrivilegedApps.add(logicalIndex, pkgName); + if (!changedPkgs.remove(pkgName)) { + // The package wasn't in the previous set of privileged apps. Add it + // since its state has changed. + changedPkgs.add(pkgName); + } + } + } + + // The carrier privileged list doesn't provide a simple userId correlation, + // so for now, use USER_ALL for these packages. + // TODO(141645789): use the UID list to narrow down to specific userIds + updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); + } + } + public void dump(@NonNull IndentingPrintWriter pw) { pw.println("Special apps:"); pw.increaseIndent(); synchronized (mSatLock) { for (int u = 0; u < mSpecialApps.size(); ++u) { + pw.print("User "); pw.print(mSpecialApps.keyAt(u)); pw.print(": "); pw.println(mSpecialApps.valuesAt(u)); } + pw.println(); + pw.println("Carrier privileged packages:"); + pw.increaseIndent(); + for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) { + pw.print(mCarrierPrivilegedApps.keyAt(i)); + pw.print(": "); + pw.println(mCarrierPrivilegedApps.valuesAt(i)); + } + pw.decreaseIndent(); + pw.println(); pw.print("Power allowlisted packages: "); pw.println(mPowerAllowlistedApps); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 6bcd778c234b..c6a6865f1cf1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -60,10 +60,13 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.job.JobInfo; @@ -71,12 +74,15 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.net.NetworkRequest; import android.os.Looper; import android.os.PowerManager; +import android.os.UserHandle; import android.provider.DeviceConfig; +import android.telephony.TelephonyManager; +import android.telephony.UiccSlotMapping; import android.util.ArraySet; import android.util.EmptyArray; import android.util.SparseArray; @@ -104,6 +110,9 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; public class FlexibilityControllerTest { @@ -113,6 +122,9 @@ public class FlexibilityControllerTest { private MockitoSession mMockingSession; private BroadcastReceiver mBroadcastReceiver; + private final SparseArray> mCarrierPrivilegedApps = new SparseArray<>(); + private final SparseArray + mCarrierPrivilegedCallbacks = new SparseArray<>(); private FlexibilityController mFlexibilityController; private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private JobStore mJobStore; @@ -130,6 +142,10 @@ public class FlexibilityControllerTest { @Mock private PrefetchController mPrefetchController; @Mock + private TelephonyManager mTelephonyManager; + @Mock + private IPackageManager mIPackageManager; + @Mock private PackageManager mPackageManager; @Before @@ -138,6 +154,7 @@ public class FlexibilityControllerTest { .initMocks(this) .strictness(Strictness.LENIENT) .spyStatic(DeviceConfig.class) + .mockStatic(AppGlobals.class) .mockStatic(LocalServices.class) .startMocking(); // Called in StateController constructor. @@ -167,17 +184,23 @@ public class FlexibilityControllerTest { -> mDeviceConfigPropertiesBuilder.build()) .when(() -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.any())); + // Used in FlexibilityController.SpecialAppTracker. + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) + .thenReturn(true); //used to get jobs by UID mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); doReturn(mJobStore).when(mJobSchedulerService).getJobStore(); // Used in JobStatus. - doReturn(mock(PackageManagerInternal.class)) - .when(() -> LocalServices.getService(PackageManagerInternal.class)); + doReturn(mIPackageManager).when(AppGlobals::getPackageManager); // Freeze the clocks at a moment in time JobSchedulerService.sSystemClock = Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); + // Set empty set of privileged apps. + setSimSlotMappings(null); + setPowerWhitelistExceptIdle(); // Initialize real objects. doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any()); ArgumentCaptor receiverCaptor = @@ -249,9 +272,13 @@ public class FlexibilityControllerTest { } private JobStatus createJobStatus(String testTag, JobInfo.Builder job) { + return createJobStatus(testTag, job, SOURCE_PACKAGE); + } + + private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) { JobInfo jobInfo = job.build(); JobStatus js = JobStatus.createFromJobInfo( - jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag); + jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag); js.enqueueTime = FROZEN_TIME; js.setStandbyBucket(ACTIVE_INDEX); if (js.hasFlexibilityConstraint()) { @@ -1084,7 +1111,6 @@ public class FlexibilityControllerTest { @Test public void testAllowlistedAppBypass() { - setPowerWhitelistExceptIdle(); mFlexibilityController.onSystemServicesReady(); JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", @@ -1117,6 +1143,148 @@ public class FlexibilityControllerTest { } } + @Test + public void testCarrierPrivilegedAppBypass() throws Exception { + mFlexibilityController.onSystemServicesReady(); + + final String carrier1Pkg1 = "com.test.carrier.1.pkg.1"; + final String carrier1Pkg2 = "com.test.carrier.1.pkg.2"; + final String carrier2Pkg = "com.test.carrier.2.pkg"; + final String nonCarrierPkg = "com.test.normal.pkg"; + + setPackageUid(carrier1Pkg1, 1); + setPackageUid(carrier1Pkg2, 11); + setPackageUid(carrier2Pkg, 2); + setPackageUid(nonCarrierPkg, 3); + + // Set the second carrier's privileged list before SIM configuration is sent to test + // initialization. + setCarrierPrivilegedAppList(2, carrier2Pkg); + + UiccSlotMapping sim1 = mock(UiccSlotMapping.class); + UiccSlotMapping sim2 = mock(UiccSlotMapping.class); + doReturn(1).when(sim1).getLogicalSlotIndex(); + doReturn(2).when(sim2).getLogicalSlotIndex(); + setSimSlotMappings(List.of(sim1, sim2)); + + JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1); + JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1); + JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1); + JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1); + JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2); + JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2); + JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2); + JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2); + JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg); + JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg); + JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg); + JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg); + JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg); + JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg); + JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg); + JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg); + + setCarrierPrivilegedAppList(1); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Only mark the first package of carrier 1 as privileged. Only that app's jobs should + // be exempted. + setCarrierPrivilegedAppList(1, carrier1Pkg1); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Add the second package of carrier 1. Both apps' jobs should be exempted. + setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Remove a SIM slot. The relevant app's should no longer have exempted jobs. + setSimSlotMappings(List.of(sim1)); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + } + @Test public void testForegroundAppBypass() { JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", @@ -1753,6 +1921,24 @@ public class FlexibilityControllerTest { } } + private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) { + final ArraySet packageSet = packages == null + ? new ArraySet<>() : new ArraySet<>(packages); + mCarrierPrivilegedApps.put(logicalIndex, packageSet); + + TelephonyManager.CarrierPrivilegesCallback callback = + mCarrierPrivilegedCallbacks.get(logicalIndex); + if (callback != null) { + callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet()); + waitForQuietModuleThread(); + } + } + + private void setPackageUid(final String pkgName, final int uid) throws Exception { + doReturn(uid).when(mIPackageManager) + .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid))); + } + private void setPowerWhitelistExceptIdle(String... packages) { doReturn(packages == null ? EmptyArray.STRING : packages) .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle(); @@ -1763,6 +1949,47 @@ public class FlexibilityControllerTest { } } + private void setSimSlotMappings(@Nullable Collection simSlotMapping) { + clearInvocations(mTelephonyManager); + final Collection returnedMapping = simSlotMapping == null + ? Collections.emptyList() : simSlotMapping; + doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping(); + if (mBroadcastReceiver != null) { + final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + mBroadcastReceiver.onReceive(mContext, intent); + waitForQuietModuleThread(); + } + if (returnedMapping.size() > 0) { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class); + ArgumentCaptor logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class); + + final int minExpectedNewRegistrations = Math.max(0, + returnedMapping.size() - mCarrierPrivilegedCallbacks.size()); + verify(mTelephonyManager, atLeast(minExpectedNewRegistrations)) + .registerCarrierPrivilegesCallback( + logicalIndexCaptor.capture(), any(), callbackCaptor.capture()); + + final List registeredIndices = logicalIndexCaptor.getAllValues(); + final List registeredCallbacks = + callbackCaptor.getAllValues(); + for (int i = 0; i < registeredIndices.size(); ++i) { + final int logicalIndex = registeredIndices.get(i); + final TelephonyManager.CarrierPrivilegesCallback callback = + registeredCallbacks.get(i); + + mCarrierPrivilegedCallbacks.put(logicalIndex, callback); + + // The API contract promises a callback upon registration with the current list. + final ArraySet cpApps = mCarrierPrivilegedApps.get(logicalIndex); + callback.onCarrierPrivilegesChanged( + cpApps == null ? Collections.emptySet() : cpApps, + Collections.emptySet()); + } + waitForQuietModuleThread(); + } + } + private void setUidBias(int uid, int bias) { int prevBias = mJobSchedulerService.getUidBias(uid); doReturn(bias).when(mJobSchedulerService).getUidBias(uid); -- cgit v1.2.3-59-g8ed1b