From fbe94c3b0047376517f085c1c91a221180cc1853 Mon Sep 17 00:00:00 2001 From: Junyu Lai Date: Thu, 24 Apr 2025 13:58:08 +0800 Subject: [PATCH] Maintain a set self-certified apps in SatelliteAccessController This is needed for tracking the applications which opted-in to use the constrained data satellite network as its default by adding the following property to the tag of its manifest: Flag: EXEMPT flagged with a kill switch Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.connectivity.SatelliteAccessControllerTest \ --update-device Bug: 410731080 Change-Id: I00c0ce5e0d7aac1192272e79a19fbab4e3a5e6f1 --- .../android/server/ConnectivityService.java | 9 + .../connectivity/ConnectivityFlags.java | 3 + .../SatelliteAccessController.java | 170 ++++++++++++++---- .../SatelliteAccessControllerTest.kt | 168 +++++++++++++++-- 4 files changed, 304 insertions(+), 46 deletions(-) diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index f1b701f25e..f691b04606 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -7735,6 +7735,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final List apps = mContext.getPackageManager() .getInstalledPackagesAsUser(GET_PERMISSIONS, user.getIdentifier()); mPermissionMonitor.onUserAddedWithInstalledPackageList(user, apps); + if (mSatelliteAccessController != null) { + mSatelliteAccessController.onUserAddedWithInstalledPackageList(user, apps); + } } @Override @@ -7757,12 +7760,18 @@ public class ConnectivityService extends IConnectivityManager.Stub public void onPackageAdded(@NonNull final String packageName, final int uid) { handlePackageChanged(packageName); mPermissionMonitor.onPackageAdded(packageName, uid); + if (mSatelliteAccessController != null) { + mSatelliteAccessController.onPackageAdded(packageName, uid); + } } @Override public void onPackageRemoved(@NonNull final String packageName, final int uid) { handlePackageChanged(packageName); mPermissionMonitor.onPackageRemoved(packageName, uid); + if (mSatelliteAccessController != null) { + mSatelliteAccessController.onPackageRemoved(packageName, uid); + } } @Override diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java index fd24951049..1655328816 100644 --- a/service/src/com/android/server/connectivity/ConnectivityFlags.java +++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java @@ -67,6 +67,9 @@ public final class ConnectivityFlags { public static final String CLOSE_QUIC_CONNECTION = "close_quic_connection"; + public static final String CONSTRAINED_DATA_SATELLITE_OPTIN = + "constrained_data_satellite_optin"; + private boolean mNoRematchAllRequestsOnRegister; /** diff --git a/service/src/com/android/server/connectivity/SatelliteAccessController.java b/service/src/com/android/server/connectivity/SatelliteAccessController.java index 65993909a1..5e6d5cc603 100644 --- a/service/src/com/android/server/connectivity/SatelliteAccessController.java +++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java @@ -16,22 +16,27 @@ package com.android.server.connectivity; +import static android.os.Process.INVALID_UID; + +import static com.android.server.connectivity.ConnectivityFlags.CONSTRAINED_DATA_SATELLITE_OPTIN; + import android.Manifest; import android.annotation.NonNull; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; -import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.DeviceConfigUtils; import java.util.List; import java.util.Set; @@ -45,17 +50,32 @@ import java.util.function.Consumer; */ public class SatelliteAccessController { private static final String TAG = SatelliteAccessController.class.getSimpleName(); + // Shamelessly copied from telephony/satellite/SatelliteManager.java. + @VisibleForTesting + public static final String PROPERTY_SATELLITE_DATA_OPTIMIZED = + "android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"; + // This value is taken from android.os.UserHandle#PER_USER_RANGE. + @VisibleForTesting + public static final int PER_USER_RANGE = 100000; private final Context mContext; private final Dependencies mDeps; private final DefaultMessageRoleListener mDefaultMessageRoleListener; private final Consumer> mCallback; private final Handler mConnectivityServiceHandler; + private final PackageManager mPackageManager; + private final boolean mSupportConstrainedDataSatelliteOptIn; // At this sparseArray, Key is userId and values are uids of SMS apps that are allowed // to use satellite network as fallback. private final SparseArray> mAllUsersSatelliteNetworkFallbackUidCache = new SparseArray<>(); + // Set of UIDs that have declared the + // {@code android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED} property + // with a value of package name in their manifest file. This variable will only be + // accessed on the handler thread. + private final Set mSatelliteDataOptimizedUids = new ArraySet<>(); + /** * Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String, * UserHandle)}, @@ -104,6 +124,12 @@ public class SatelliteAccessController { @NonNull OnRoleHoldersChangedListener listener, UserHandle user) { mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user); } + + /** Return whether constrained data satellite opt-in is supported. */ + public boolean supportConstrainedDataSatelliteOptIn(Context context) { + return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, + CONSTRAINED_DATA_SATELLITE_OPTIN); + } } @VisibleForTesting @@ -115,6 +141,8 @@ public class SatelliteAccessController { mDefaultMessageRoleListener = new DefaultMessageRoleListener(); mCallback = callback; mConnectivityServiceHandler = connectivityServiceInternalHandler; + mPackageManager = mContext.getPackageManager(); + mSupportConstrainedDataSatelliteOptIn = mDeps.supportConstrainedDataSatelliteOptIn(c); } private Set updateSatelliteNetworkFallbackUidListCache(List packageNames, @@ -129,7 +157,7 @@ public class SatelliteAccessController { // cache list. if (isSatellitePermissionEnabled(pm, packageName)) { int uid = getUidForPackage(pm, packageName); - if (uid != Process.INVALID_UID) { + if (uid != INVALID_UID) { fallbackUids.add(uid); } } @@ -150,7 +178,7 @@ public class SatelliteAccessController { private int getUidForPackage(PackageManager packageManager, String pkgName) { if (pkgName == null) { - return Process.INVALID_UID; + return INVALID_UID; } try { ApplicationInfo applicationInfo = packageManager.getApplicationInfo(pkgName, 0); @@ -158,13 +186,13 @@ public class SatelliteAccessController { } catch (PackageManager.NameNotFoundException exception) { Log.e(TAG, "Unable to find uid for package: " + pkgName); } - return Process.INVALID_UID; + return INVALID_UID; } // on Role sms change triggered by OnRoleHoldersChangedListener() private void onRoleSmsChanged(@NonNull UserHandle userHandle) { int userId = userHandle.getIdentifier(); - if (userId == Process.INVALID_UID) { + if (userId == INVALID_UID) { Log.wtf(TAG, "Invalid User Id"); return; } @@ -209,34 +237,10 @@ public class SatelliteAccessController { } public void start() { - mConnectivityServiceHandler.post(this::updateAllUserRoleSmsUids); - // register sms OnRoleHoldersChangedListener mDefaultMessageRoleListener.register(); } - private void updateAllUserRoleSmsUids() { - UserManager userManager = mContext.getSystemService(UserManager.class); - // get existing user handles of available users - List existingUsers = userManager.getUserHandles(true /*excludeDying*/); - - // Iterate through the user handles and obtain their uids with role sms and satellite - // communication permission - Log.i(TAG, "existing users: " + existingUsers); - for (UserHandle userHandle : existingUsers) { - onRoleSmsChanged(userHandle); - } - } - - /** - * Called when a user is removed. See {link #ACTION_USER_REMOVED}. - * - * @param userHandle The userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}. - */ - public void onUserRemoved(@NonNull UserHandle userHandle) { - updateSatelliteFallbackUidListOnUserRemoval(userHandle.getIdentifier()); - } - private void updateSatelliteFallbackUidListOnUserRemoval(int userIdRemoved) { Log.i(TAG, "user id removed:" + userIdRemoved); if (mAllUsersSatelliteNetworkFallbackUidCache.contains(userIdRemoved)) { @@ -244,4 +248,110 @@ public class SatelliteAccessController { reportSatelliteNetworkFallbackUids(); } } + + /** + * Called when a user is added. See {link #ACTION_USER_ADDED}. + * + * Note that this method will also be called at start up once on the handler thread + * to iterate through all existing users. + * + * @param userHandle The userHandle of the added user. See {@link #EXTRA_USER_HANDLE}. + * @param apps The list of packages which is installed on the user. + */ + public void onUserAddedWithInstalledPackageList(@NonNull UserHandle userHandle, + @NonNull List apps) { + // Obtain uids with role sms and satellite communication permission for the added user. + onRoleSmsChanged(userHandle); + + if (!mSupportConstrainedDataSatelliteOptIn) return; + + final Set satelliteDataOptimizedAppsForUser = + getSatelliteDataOptimizedAppsForUser(apps); + Log.i(TAG, "Add SatelliteDataOptimizedApps + for user " + userHandle + ": " + + satelliteDataOptimizedAppsForUser); + mSatelliteDataOptimizedUids.addAll(satelliteDataOptimizedAppsForUser); + } + + /** + * Called when a user is removed. See {link #ACTION_USER_REMOVED}. + * + * @param userHandle The integer userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}. + */ + public void onUserRemoved(@NonNull UserHandle userHandle) { + updateSatelliteFallbackUidListOnUserRemoval(userHandle.getIdentifier()); + if (mSupportConstrainedDataSatelliteOptIn) { + removeSatelliteDataOptimizedAppsForUser(userHandle.getIdentifier()); + } + } + + /** + * Called when a package is added. + * + * @param packageName The name of the new package. + * @param uid The uid of the new package. + */ + public void onPackageAdded(@NonNull final String packageName, final int uid) { + if (mSupportConstrainedDataSatelliteOptIn && isSatelliteDataOptimizedApp(packageName)) { + mSatelliteDataOptimizedUids.add(uid); + } + } + + /** + * Called when a package is removed. + * + * @param packageName The name of the removed package or null. + * @param uid containing the integer uid previously assigned to the package. + */ + public void onPackageRemoved(@NonNull final String packageName, final int uid) { + if (!mSupportConstrainedDataSatelliteOptIn) return; + + // Scan for all apps sharing the same uid. + final String [] pkgs = mPackageManager.getPackagesForUid(uid); + if (pkgs != null) { + for (String pkg : pkgs) { + if (!pkg.equals(packageName) && isSatelliteDataOptimizedApp(pkg)) { + return; // Early return if another satellite-optimized app shares the UID + } + } + } + // If the loop completes without returning, it means no other + // satellite-optimized app shares the UID. + mSatelliteDataOptimizedUids.remove(uid); + } + + /** Checks if the given UID is in the set of satellite data optimized UIDs. */ + @VisibleForTesting + public boolean isSatelliteDataOptimizedUid(int uid) { + return mSatelliteDataOptimizedUids.contains(uid); + } + + /** Gets the number of satellite data optimized UIDs being tracked. */ + @VisibleForTesting + public int getSatelliteDataOptimizedUidCount() { + return mSatelliteDataOptimizedUids.size(); + } + + @NonNull + private Set getSatelliteDataOptimizedAppsForUser(@NonNull List apps) { + final ArraySet uids = new ArraySet<>(); + for (PackageInfo app : apps) { + if (null == app.applicationInfo || app.applicationInfo.uid < 0) continue; + if (isSatelliteDataOptimizedApp(app.packageName)) uids.add(app.applicationInfo.uid); + } + return uids; + } + + private boolean isSatelliteDataOptimizedApp(@NonNull String packageName) { + try { + final PackageManager.Property property = mPackageManager + .getProperty(PROPERTY_SATELLITE_DATA_OPTIMIZED, packageName); + return property.isString() && TextUtils.equals(property.getString(), packageName); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private void removeSatelliteDataOptimizedAppsForUser(int userIdToRemove) { + mSatelliteDataOptimizedUids.removeIf(uid -> uid / PER_USER_RANGE == userIdToRemove); + } } diff --git a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt index f5d1ddddb9..578b880346 100644 --- a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt +++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt @@ -20,6 +20,7 @@ import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.content.Context import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.os.Build import android.os.Handler @@ -27,12 +28,21 @@ import android.os.Looper import android.os.UserHandle import android.os.UserManager import android.util.ArraySet +import com.android.server.connectivity.ConnectivityFlags.CONSTRAINED_DATA_SATELLITE_OPTIN +import com.android.server.connectivity.SatelliteAccessController.PER_USER_RANGE +import com.android.server.connectivity.SatelliteAccessController.PROPERTY_SATELLITE_DATA_OPTIMIZED import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.com.android.testutils.SetFeatureFlagsRule +import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor import java.util.function.Consumer +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -42,9 +52,9 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import org.mockito.Mockito.never -import org.mockito.Mockito.timeout import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` private const val PRIMARY_USER = 0 private const val SECONDARY_USER = 10 @@ -69,22 +79,38 @@ private val PRIMARY_USER_SMS_APP_UID2 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ private val SECONDARY_USER_SMS_APP_UID1 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID1) private val SECONDARY_USER_SMS_APP_UID2 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID2) +private const val TEST_PACKAGE1 = "com.android.package1" +private const val TEST_PACKAGE2 = "com.android.package2" +private const val TEST_UID1 = 2001 +private const val TEST_UID2 = 2002 + SECONDARY_USER * PER_USER_RANGE // Under 2nd user. + @RunWith(DevSdkIgnoreRunner::class) @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) class SatelliteAccessControllerTest { private val context = mock(Context::class.java) private val primaryUserContext = mock(Context::class.java) private val secondaryUserContext = mock(Context::class.java) + private val packageManager = mock(PackageManager::class.java) private val mPackageManagerPrimaryUser = mock(PackageManager::class.java) private val mPackageManagerSecondaryUser = mock(PackageManager::class.java) private val mDeps = mock(SatelliteAccessController.Dependencies::class.java) private val mCallback = mock(Consumer::class.java) as Consumer> private val userManager = mock(UserManager::class.java) private val mHandler = Handler(Looper.getMainLooper()) - private var mSatelliteAccessController = - SatelliteAccessController(context, mDeps, mCallback, mHandler) + private lateinit var mSatelliteAccessController: SatelliteAccessController private lateinit var mRoleHolderChangedListener: OnRoleHoldersChangedListener + private val featureFlags = HashSet() + + // This will set feature flags from @FeatureFlag annotations + // into the map before setUp() runs. + @get:Rule + val setFeatureFlagsRule = SetFeatureFlagsRule( + { name, enabled -> + if (enabled == true) featureFlags.add(name) else featureFlags.remove(name) }, + { name -> featureFlags.contains(name) } + ) + private fun mockService(name: String, clazz: Class, service: T) { doReturn(name).`when`(context).getSystemServiceName(clazz) doReturn(service).`when`(context).getSystemService(name) @@ -99,6 +125,10 @@ class SatelliteAccessControllerTest { fun setup() { doReturn(emptyList()).`when`(userManager).getUserHandles(true) mockService(Context.USER_SERVICE, UserManager::class.java, userManager) + doReturn(packageManager).`when`(context).packageManager + doReturn(featureFlags.contains(CONSTRAINED_DATA_SATELLITE_OPTIN)) + .`when`(mDeps).supportConstrainedDataSatelliteOptIn(any()) + mSatelliteAccessController = SatelliteAccessController(context, mDeps, mCallback, mHandler) doReturn(primaryUserContext).`when`(context).createContextAsUser(PRIMARY_USER_HANDLE, 0) doReturn(mPackageManagerPrimaryUser).`when`(primaryUserContext).packageManager @@ -298,19 +328,6 @@ class SatelliteAccessControllerTest { return future.get() } - @Test - fun testOnStartUpCallbackSatelliteFallbackUidWithExistingUsers() { - doReturn( - listOf(PRIMARY_USER_HANDLE) - ).`when`(userManager).getUserHandles(true) - doReturn(listOf(SMS_APP1)) - .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE) - // At start up, SatelliteAccessController must call CS callback with existing users' - // default messaging apps uids. - startSatelliteAccessController() - verify(mCallback, timeout(500)).accept(setOf(PRIMARY_USER_SMS_APP_UID1)) - } - private fun startSatelliteAccessController() { mSatelliteAccessController.start() // Get registered listener using captor @@ -322,4 +339,123 @@ class SatelliteAccessControllerTest { ) mRoleHolderChangedListener = listenerCaptor.value } + + private fun makePackageInfo(packageName: String, uid: Int) = PackageInfo().apply { + this.packageName = packageName + applicationInfo = ApplicationInfo().apply { this.uid = uid } + } + + private fun mockGetPackagesForUid(uid: Int, pkgs: Array?) { + `when`(packageManager.getPackagesForUid(uid)).thenReturn(pkgs) + } + + private fun mockIsSatelliteDataOptimizedApp(packageName: String, isOptimized: Boolean) { + val property = mock(PackageManager.Property::class.java) + `when`(property.isString).thenReturn(isOptimized) + if (isOptimized) { + `when`(property.string).thenReturn(packageName) + } + `when`(packageManager.getProperty( + eq(PROPERTY_SATELLITE_DATA_OPTIMIZED), + eq(packageName) + )).thenReturn(property) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN) + @Test + fun testSatelliteOptimizedUids_onPackageAdded() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, true) + mSatelliteAccessController.onPackageAdded(TEST_PACKAGE1, TEST_UID1) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 1) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN) + @Test + fun testSatelliteOptimizedUids_onPackageAdded_ignoresIfNotSatelliteOptimized() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, false) + mSatelliteAccessController.onPackageAdded(TEST_PACKAGE1, TEST_UID1) + assertFalse(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN) + @Test + fun testSatelliteOptimizedUids_onPackageRemoved_noOtherShareUid() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, true) + mockGetPackagesForUid(TEST_UID1, arrayOf(TEST_PACKAGE1)) + + mSatelliteAccessController.onPackageAdded(TEST_PACKAGE1, TEST_UID1) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 1) + mSatelliteAccessController.onPackageRemoved(TEST_PACKAGE1, TEST_UID1) + assertFalse(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN) + @Test + fun testSatelliteOptimizedUids_onPackageRemoved_otherShareUid() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, true) + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE2, true) + mockGetPackagesForUid(TEST_UID1, arrayOf(TEST_PACKAGE1, TEST_PACKAGE2)) + + // Verify uid is not removed if there is still another package shares the same uid. + mSatelliteAccessController.onPackageAdded(TEST_PACKAGE1, TEST_UID1) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 1) + mSatelliteAccessController.onPackageRemoved(TEST_PACKAGE1, TEST_UID1) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 1) + + // Verify uid is removed if there is no other package with shared uid. + mockGetPackagesForUid(TEST_UID1, null) + mSatelliteAccessController.onPackageRemoved(TEST_PACKAGE2, TEST_UID1) + assertFalse(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN) + @Test + fun testSatelliteOptimizedUids_onUserAddedRemoved() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, true) + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE2, true) + val packageInfo1 = makePackageInfo(TEST_PACKAGE1, TEST_UID1) + val packageInfo2 = makePackageInfo(TEST_PACKAGE2, TEST_UID2) + + mSatelliteAccessController + .onUserAddedWithInstalledPackageList(PRIMARY_USER_HANDLE, listOf(packageInfo1)) + mSatelliteAccessController + .onUserAddedWithInstalledPackageList(SECONDARY_USER_HANDLE, listOf(packageInfo2)) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID2)) + + mSatelliteAccessController.onUserRemoved(SECONDARY_USER_HANDLE) + // Verify that the app associated with the non-removed user is not removed. + assertTrue(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID1)) + // Verify that the app associated with the removed user is removed. + assertFalse(mSatelliteAccessController.isSatelliteDataOptimizedUid(TEST_UID2)) + + mSatelliteAccessController.onUserRemoved(PRIMARY_USER_HANDLE) + // Verify everything is removed. + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + } + + @FeatureFlag(name = CONSTRAINED_DATA_SATELLITE_OPTIN, enabled = false) + @Test + fun testSatelliteOptimizedUids_featureDisabled() { + mockIsSatelliteDataOptimizedApp(TEST_PACKAGE1, true) + val packageInfo1 = makePackageInfo(TEST_PACKAGE1, TEST_UID1) + + // Verify nothing changes and nothing crashes. + mSatelliteAccessController + .onUserAddedWithInstalledPackageList(PRIMARY_USER_HANDLE, listOf(packageInfo1)) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + mSatelliteAccessController.onPackageAdded(TEST_PACKAGE1, TEST_UID1) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + mSatelliteAccessController.onPackageRemoved(TEST_PACKAGE1, TEST_UID1) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + mSatelliteAccessController.onUserRemoved(PRIMARY_USER_HANDLE) + assertEquals(mSatelliteAccessController.satelliteDataOptimizedUidCount, 0) + } }