Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a236a123 authored by Rambo Wang's avatar Rambo Wang Committed by Gerrit Code Review
Browse files

Merge "Support carrier privileges linger period in CarrierPrivilegesTracker"

parents 34a07171 04cf19b1
Loading
Loading
Loading
Loading
+113 −24
Original line number Diff line number Diff line
@@ -21,12 +21,17 @@ import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
import static android.telephony.TelephonyManager.EXTRA_SIM_STATE;
import static android.telephony.TelephonyManager.SIM_STATE_ABSENT;
import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
import static android.telephony.TelephonyManager.SIM_STATE_NOT_READY;
import static android.telephony.TelephonyManager.SIM_STATE_READY;
import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -48,6 +53,7 @@ import android.os.Message;
import android.os.PersistableBundle;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.Annotation.CarrierPrivilegeStatus;
@@ -79,6 +85,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
@@ -96,6 +103,12 @@ public class CarrierPrivilegesTracker extends Handler {
    private static final String SHA_1 = "SHA-1";
    private static final String SHA_256 = "SHA-256";

    /**
     * Time delay to clear UICC rules after UICC is gone.
     * This introduces the grace period to retain carrier privileges when SIM is removed.
     */
    private static final long CLEAR_UICC_RULES_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(30);

    /**
     * Action to register a Registrant with this Tracker.
     * obj: Registrant that will be notified of Carrier Privileged UID changes.
@@ -145,6 +158,11 @@ public class CarrierPrivilegesTracker extends Handler {
     */
    private static final int ACTION_SET_TEST_OVERRIDE_RULE = 8;

    /**
     * Action to clear UICC rules.
     */
    private static final int ACTION_CLEAR_UICC_RULES = 9;

    private final Context mContext;
    private final Phone mPhone;
    private final PackageManager mPackageManager;
@@ -169,11 +187,30 @@ public class CarrierPrivilegesTracker extends Handler {
    // Map of PackageName -> UIDs for that Package
    @NonNull private final Map<String, Set<Integer>> mCachedUids = new ArrayMap<>();

    // This should be used to guard critical section either with
    // mPrivilegedPackageInfoLock.readLock() or mPrivilegedPackageInfoLock.writeLock(), but never
    // with the mPrivilegedPackageInfoLock object itself.
    @NonNull private final ReadWriteLock mPrivilegedPackageInfoLock = new ReentrantReadWriteLock();
    // Package names and UIDs of apps that currently hold carrier privileges.
    @GuardedBy({"mPrivilegedPackageInfoLock.readLock()", "mPrivilegedPackageInfoLock.writeLock()"})
    @GuardedBy(anyOf = {"mPrivilegedPackageInfoLock.readLock()",
            "mPrivilegedPackageInfoLock.writeLock()"})
    @NonNull private PrivilegedPackageInfo mPrivilegedPackageInfo = new PrivilegedPackageInfo();

    // Uptime in millis on when the NEXT clear-up of UiccRules are scheduled
    @ElapsedRealtimeLong
    private long mClearUiccRulesUptimeMillis = CLEAR_UICC_RULE_NOT_SCHEDULED;
    // Constant indicates no schedule to clear UiccRules
    private static final long CLEAR_UICC_RULE_NOT_SCHEDULED = -1;

    // Indicates SIM has reached SIM_STATE_READY but not SIM_STATE_LOADED yet. During this transient
    // state, all the information previously loaded from SIM may be updated soon later and thus
    // unreliable. For security's concern, any carrier privileges check should return
    // CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED (instead of neither HAS_ACCESS nor NO_ACCESS) until
    // SIM becomes LOADED again, or grace period specified by CLEAR_UICC_RULES_DELAY_MILLIS expires.
    @GuardedBy(anyOf = {"mPrivilegedPackageInfoLock.readLock()",
            "mPrivilegedPackageInfoLock.writeLock()"})
    private boolean mSimIsReadyButNotLoaded = false;

    /** Small snapshot to hold package names and UIDs of privileged packages. */
    private static final class PrivilegedPackageInfo {
        @NonNull final Set<String> mPackageNames;
@@ -250,7 +287,10 @@ public class CarrierPrivilegesTracker extends Handler {

                            if (simState != SIM_STATE_ABSENT
                                    && simState != SIM_STATE_NOT_READY
                                    && simState != SIM_STATE_LOADED) return;
                                    && simState != SIM_STATE_READY
                                    && simState != SIM_STATE_LOADED) {
                                return;
                            }

                            sendMessage(obtainMessage(ACTION_SIM_STATE_UPDATED, slotId, simState));
                            break;
@@ -349,6 +389,10 @@ public class CarrierPrivilegesTracker extends Handler {
                handleSetTestOverrideRules(carrierPrivilegeRules);
                break;
            }
            case ACTION_CLEAR_UICC_RULES: {
                handleClearUiccRules();
                break;
            }
            default: {
                Rlog.e(TAG, "Received unknown msg type: " + msg.what);
                break;
@@ -411,16 +455,45 @@ public class CarrierPrivilegesTracker extends Handler {

        List<UiccAccessRule> updatedUiccRules = Collections.EMPTY_LIST;

        mPrivilegedPackageInfoLock.writeLock().lock();
        try {
            mSimIsReadyButNotLoaded = simState == SIM_STATE_READY;
        } finally {
            mPrivilegedPackageInfoLock.writeLock().unlock();
        }

        // Only include the UICC rules if the SIM is fully loaded
        if (simState == SIM_STATE_LOADED) {
            mClearUiccRulesUptimeMillis = CLEAR_UICC_RULE_NOT_SCHEDULED;
            removeMessages(ACTION_CLEAR_UICC_RULES);

            updatedUiccRules = getSimRules();
        }

        mLocalLog.log("SIM State Changed:"
            mLocalLog.log("SIM fully loaded:"
                    + " slotId=" + slotId
                    + " simState=" + simState
                    + " updated SIM-loaded rules=" + updatedUiccRules);
            maybeUpdateRulesAndNotifyRegistrants(mUiccRules, updatedUiccRules);
        } else {
            if (!mUiccRules.isEmpty()
                    && mClearUiccRulesUptimeMillis == CLEAR_UICC_RULE_NOT_SCHEDULED) {
                mClearUiccRulesUptimeMillis =
                        SystemClock.uptimeMillis() + CLEAR_UICC_RULES_DELAY_MILLIS;
                sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
                        mClearUiccRulesUptimeMillis);
                mLocalLog.log("SIM is gone. Delay " + TimeUnit.MILLISECONDS.toSeconds(
                        CLEAR_UICC_RULES_DELAY_MILLIS) + " seconds to clear UICC rules.");
            } else {
                mLocalLog.log(
                        "Ignore SIM gone event while UiccRules is empty or waiting to be emptied.");
            }
        }
    }

    private void handleClearUiccRules() {
        mClearUiccRulesUptimeMillis = CLEAR_UICC_RULE_NOT_SCHEDULED;
        removeMessages(ACTION_CLEAR_UICC_RULES);
        maybeUpdateRulesAndNotifyRegistrants(mUiccRules, Collections.EMPTY_LIST);
    }

    @NonNull
@@ -683,6 +756,7 @@ public class CarrierPrivilegesTracker extends Handler {
            pw.println(
                    "CarrierPrivilegesTracker - Privileged package info: "
                            + mPrivilegedPackageInfo);
            pw.println("mSimIsReadyButNotLoaded: " + mSimIsReadyButNotLoaded);
        } finally {
            mPrivilegedPackageInfoLock.readLock().unlock();
        }
@@ -696,6 +770,7 @@ public class CarrierPrivilegesTracker extends Handler {
                                    mInstalledPackageCerts.entrySet(),
                                    e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue()));
        }
        pw.println("mClearUiccRulesUptimeMillis: " + mClearUiccRulesUptimeMillis);
    }

    /**
@@ -766,9 +841,13 @@ public class CarrierPrivilegesTracker extends Handler {
        // errors) always supersede them unless something goes super wrong when getting CC.
        mPrivilegedPackageInfoLock.readLock().lock();
        try {
            return mPrivilegedPackageInfo.mPackageNames.contains(packageName)
                    ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                    : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
            if (mSimIsReadyButNotLoaded) {
                return CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
            } else if (mPrivilegedPackageInfo.mPackageNames.contains(packageName)) {
                return CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
            } else {
                return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
            }
        } finally {
            mPrivilegedPackageInfoLock.readLock().unlock();
        }
@@ -779,7 +858,8 @@ public class CarrierPrivilegesTracker extends Handler {
    public Set<String> getPackagesWithCarrierPrivileges() {
        mPrivilegedPackageInfoLock.readLock().lock();
        try {
            return Collections.unmodifiableSet(mPrivilegedPackageInfo.mPackageNames);
            return mSimIsReadyButNotLoaded ? Collections.emptySet() :
                    Collections.unmodifiableSet(mPrivilegedPackageInfo.mPackageNames);
        } finally {
            mPrivilegedPackageInfoLock.readLock().unlock();
        }
@@ -796,9 +876,13 @@ public class CarrierPrivilegesTracker extends Handler {
        // errors) always supersede them unless something goes super wrong when getting CC.
        mPrivilegedPackageInfoLock.readLock().lock();
        try {
            return ArrayUtils.contains(mPrivilegedPackageInfo.mUids, uid)
                    ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                    : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
            if (mSimIsReadyButNotLoaded) {
                return CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
            } else if (ArrayUtils.contains(mPrivilegedPackageInfo.mUids, uid)) {
                return CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
            } else {
                return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
            }
        } finally {
            mPrivilegedPackageInfoLock.readLock().unlock();
        }
@@ -810,6 +894,14 @@ public class CarrierPrivilegesTracker extends Handler {
     */
    @NonNull
    public List<String> getCarrierPackageNamesForIntent(@NonNull Intent intent) {
        mPrivilegedPackageInfoLock.readLock().lock();
        try {
            if (mSimIsReadyButNotLoaded) return Collections.emptyList();
        } finally {
            mPrivilegedPackageInfoLock.readLock().unlock();
        }


        // Do the PackageManager queries before we take the lock, as these are the longest-running
        // pieces of this method and don't depend on the set of carrier apps.
        List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -821,19 +913,16 @@ public class CarrierPrivilegesTracker extends Handler {
        // Now actually check which of the resolved packages have carrier privileges.
        mPrivilegedPackageInfoLock.readLock().lock();
        try {
            // Check mSimIsReadyButNotLoaded again here since the PackageManager queries above are
            // pretty time-consuming, mSimIsReadyButNotLoaded state may change since last check
            if (mSimIsReadyButNotLoaded) return Collections.emptyList();

            Set<String> packageNames = new ArraySet<>(); // For deduping purposes
            for (ResolveInfo resolveInfo : resolveInfos) {
                String packageName = getPackageName(resolveInfo);
                if (packageName == null) continue;
                switch (getCarrierPrivilegeStatusForPackage(packageName)) {
                    case TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS:
                if (packageName != null && CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                        == getCarrierPrivilegeStatusForPackage(packageName)) {
                    packageNames.add(packageName);
                        break;
                    case TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS:
                        continue;
                    default:
                        // Any other status is considered an error.
                        return Collections.emptyList();
                }
            }
            return new ArrayList<>(packageNames);
+150 −2
Original line number Diff line number Diff line
@@ -23,9 +23,13 @@ import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
import static android.telephony.TelephonyManager.EXTRA_SIM_STATE;
import static android.telephony.TelephonyManager.SIM_STATE_ABSENT;
import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
import static android.telephony.TelephonyManager.SIM_STATE_NOT_READY;
import static android.telephony.TelephonyManager.SIM_STATE_READY;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -77,9 +81,11 @@ import org.mockito.Mock;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -269,12 +275,12 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
                expectedPackageNames, mCarrierPrivilegesTracker.getPackagesWithCarrierPrivileges());
        for (String packageName : expectedPackageNames) {
            assertEquals(
                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                    CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                    mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(packageName));
        }
        for (int uid : expectedUids) {
            assertEquals(
                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                    CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                    mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForUid(uid));
        }
    }
@@ -532,6 +538,14 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_NOT_READY);
        mTestableLooper.processAllMessages();

        // Immediately check current state, nothing should change
        verifyCurrentState(Set.of(PACKAGE_1, PACKAGE_2), new int[]{UID_1, UID_2});

        // Wait for 30 seconds
        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
        mTestableLooper.processAllMessages();

        // Check again, the carrier privileges should be emptied
        verifyCurrentState(Set.of(), new int[0]);
        verifyRegistrantUpdates(new int[0], 1 /* expectedUidUpdates */);
        verifyCarrierPrivilegesChangedUpdates(
@@ -540,6 +554,140 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
                        new Pair<>(Set.of(), new int[0])));
    }

    @Test
    public void testSimStateChangedSimStateAbsentThenLoadedWithSameRules() throws Exception {
        // Start with privileges
        setupCarrierPrivilegesTrackerWithSimLoadedUids();
        // CPT initialization process may trigger notification, remove the interfere here
        reset(mTelephonyRegistryManager);

        // SIM is removed
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_ABSENT);
        mTestableLooper.processAllMessages();


        // Wait for 20 seconds and the same SIM is inserted
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
        mTestableLooper.processAllMessages();
        // Wait for another 20 seconds
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));

        // verify all carrier privileges should remain, no CP change notified
        verifyCurrentState(Set.of(PACKAGE_1, PACKAGE_2), new int[]{UID_1, UID_2});
        verifyRegistrantUpdates(null /* expectedUidUpdates */, 0 /* expectedUidUpdates */);
        verifyCarrierPrivilegesChangedUpdates(List.of());
    }

    @Test
    public void testSimStateChangedSimStateAbsentForever() throws Exception {
        // Start with privileges
        setupCarrierPrivilegesTrackerWithSimLoadedUids();
        // CPT initialization process may trigger notification, remove the interfere here
        reset(mTelephonyRegistryManager);

        // SIM is removed
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_ABSENT);
        mTestableLooper.processAllMessages();

        // Wait for 30 seconds
        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
        mTestableLooper.processAllMessages();

        // verify the carrier privileges should be emptied
        verifyCurrentState(Set.of(), new int[0]);
        verifyRegistrantUpdates(new int[0], 1 /* expectedUidUpdates */);
        verifyCarrierPrivilegesChangedUpdates(
                List.of(new Pair<>(Set.of(), new int[0])));
    }

    @Test
    public void testSimStateChangedSimStateNotReadyForever() throws Exception {
        // Start with privileges
        setupCarrierPrivilegesTrackerWithSimLoadedUids();
        // CPT initialization process may trigger notification, remove the interfere here
        reset(mTelephonyRegistryManager);

        // eSIM profile disabled and leave in state SIM_STATE_NOT_READY
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_NOT_READY);
        mTestableLooper.processAllMessages();

        // Wait for 30 seconds
        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
        mTestableLooper.processAllMessages();

        // verify the carrier privileges should be emptied
        verifyCurrentState(Set.of(), new int[0]);
        verifyRegistrantUpdates(new int[0], 1 /* expectedUidUpdates */);
        verifyCarrierPrivilegesChangedUpdates(
                List.of(new Pair<>(Set.of(), new int[0])));
    }

    @Test
    public void testSimStateChangedSimStateAbsentThenLoadedWithUpdatedRules() throws Exception {
        // Start with privileges
        setupCarrierPrivilegesTrackerWithSimLoadedUids();
        reset(mTelephonyRegistryManager);

        // SIM is removed
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_ABSENT);
        mTestableLooper.processAllMessages();


        // Wait for 20 seconds and a different SIM is inserted
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));
        setupSimLoadedRules(ruleWithHashOnly(getHash(CERT_1)));
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
        mTestableLooper.processAllMessages();
        // Wait for another 20 seconds
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));

        // Carrier privileges should be updated and CP change should be notified
        verifyCurrentState(Set.of(PACKAGE_1), new int[] {UID_1});
        verifyRegistrantUpdates(new int[] {UID_1}, 1 /* expectedUidUpdates */);
        verifyCarrierPrivilegesChangedUpdates(
                List.of(new Pair<>(Set.of(PACKAGE_1), new int[] {UID_1})));
    }

    @Test
    public void testSimStateChangedSimStateReadyThenLoaded() throws Exception {
        // Start with privileges (from carrier config)
        setupCarrierPrivilegesTrackerWithCarrierConfigUids();

        ResolveInfo pkg1ResolveInfo = new ResolveInfoBuilder().setActivity(PACKAGE_1).build();
        ResolveInfo pkg2ResolveInfo = new ResolveInfoBuilder().setActivity(PACKAGE_2).build();
        when(mPackageManager.queryBroadcastReceivers(any(), anyInt())).thenReturn(
                List.of(pkg1ResolveInfo, pkg2ResolveInfo));

        // SIM is READY
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_READY);
        mTestableLooper.processAllMessages();

        assertEquals(Collections.emptyList(),
                mCarrierPrivilegesTracker.getCarrierPackageNamesForIntent(
                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE)));
        assertEquals(CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED,
                mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForUid(UID_1));
        assertEquals(Collections.EMPTY_SET,
                mCarrierPrivilegesTracker.getPackagesWithCarrierPrivileges());
        assertEquals(CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED,
                mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(PACKAGE_1));

        // SIM is LOADED
        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
        mTestableLooper.processAllMessages();

        assertEquals(List.of(PACKAGE_1, PACKAGE_2),
                mCarrierPrivilegesTracker.getCarrierPackageNamesForIntent(
                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE)));
        assertEquals(CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForUid(UID_1));
        assertEquals(PRIVILEGED_PACKAGES,
                mCarrierPrivilegesTracker.getPackagesWithCarrierPrivileges());
        assertEquals(CARRIER_PRIVILEGE_STATUS_HAS_ACCESS,
                mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(PACKAGE_1));
    }

    @Test
    public void testSimStateChangedExplicitPackageNames() throws Exception {
        // Start with privileges specified just by wildcard certificate hashes, verify specifying