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

Commit 2480ed99 authored by Gil Cukierman's avatar Gil Cukierman
Browse files

Track identifier disclosures by subId

Safety center issues on identifier disclosures will be emitted by subId.
This change adds support for that by keeping a DisclosureWindow per
subId.

Bug: 308985417
Test: atest CellularIdentifierNotifierTest GsmCdmaPhoneTest
Change-Id: I93a861b344a5dd94a23fa871939e7e64876de204
parent a92dca74
Loading
Loading
Loading
Loading
+9 −4
Original line number Diff line number Diff line
@@ -1840,8 +1840,11 @@ public class GsmCdmaPhone extends Phone {
            boolean check = true;
            for (int itr = 0;itr < dtmfString.length(); itr++) {
                if (!PhoneNumberUtils.is12Key(dtmfString.charAt(itr))) {
                    Rlog.e(LOG_TAG,
                            "sendDtmf called with invalid character '" + dtmfString.charAt(itr)+ "'");
                    Rlog.e(
                            LOG_TAG,
                            "sendDtmf called with invalid character '"
                                    + dtmfString.charAt(itr)
                                    + "'");
                    check = false;
                    break;
                }
@@ -2848,7 +2851,9 @@ public class GsmCdmaPhone extends Phone {
            mCi.setCallWaiting(enable, serviceClass, onComplete);
        } else if (mSsOverCdmaSupported) {
            String cwPrefix = CdmaMmiCode.getCallWaitingPrefix(enable);
            Rlog.i(LOG_TAG, "setCallWaiting in CDMA : dial for set call waiting" + " prefix= " + cwPrefix);
            Rlog.i(
                    LOG_TAG,
                    "setCallWaiting in CDMA : dial for set call waiting" + " prefix= " + cwPrefix);

            PhoneAccountHandle phoneAccountHandle = subscriptionIdToPhoneAccountHandle(getSubId());
            Bundle extras = new Bundle();
@@ -3708,7 +3713,7 @@ public class GsmCdmaPhone extends Phone {
                if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
                        && mIdentifierDisclosureNotifier != null
                        && disclosure != null) {
                    mIdentifierDisclosureNotifier.addDisclosure(disclosure);
                    mIdentifierDisclosureNotifier.addDisclosure(getSubId(), disclosure);
                }
                break;

+182 −52
Original line number Diff line number Diff line
@@ -22,12 +22,14 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Encapsulates logic to emit notifications to the user that their cellular identifiers were
@@ -55,13 +57,14 @@ public class CellularIdentifierDisclosureNotifier {
    // events are strictly serialized.
    private ScheduledExecutorService mSerializedWorkQueue;

    private AtomicInteger mDisclosureCount;

    // One should only interact with this future from within the work queue's thread.
    private ScheduledFuture<?> mWhenWindowCloses;
    // This object should only be accessed from within the thread of mSerializedWorkQueue. Access
    // outside of that thread would require additional synchronization.
    private Map<Integer, DisclosureWindow> mWindows;

    public CellularIdentifierDisclosureNotifier() {
        this(Executors.newSingleThreadScheduledExecutor(), DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
        this(
                Executors.newSingleThreadScheduledExecutor(),
                DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
                TimeUnit.MINUTES);
    }

@@ -80,14 +83,14 @@ public class CellularIdentifierDisclosureNotifier {
        mSerializedWorkQueue = notificationQueue;
        mWindowCloseDuration = windowCloseDuration;
        mWindowCloseUnit = windowCloseUnit;
        mDisclosureCount = new AtomicInteger(0);
        mWindows = new HashMap<>();
    }

    /**
     * Add a CellularIdentifierDisclosure to be tracked by this instance.
     * If appropriate, this will trigger a user notification.
     * Add a CellularIdentifierDisclosure to be tracked by this instance. If appropriate, this will
     * trigger a user notification.
     */
    public void addDisclosure(CellularIdentifierDisclosure disclosure) {
    public void addDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
        Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);

        synchronized (mEnabledLock) {
@@ -108,7 +111,7 @@ public class CellularIdentifierDisclosureNotifier {
            // because we know that any actions taken on disabled will be scheduled after this
            // incrementAndNotify call.
            try {
                mSerializedWorkQueue.execute(incrementAndNotify());
                mSerializedWorkQueue.execute(incrementAndNotify(subId));
            } catch (RejectedExecutionException e) {
                Rlog.e(TAG, "Failed to schedule incrementAndNotify: " + e.getMessage());
            }
@@ -154,11 +157,6 @@ public class CellularIdentifierDisclosureNotifier {
        }
    }

    @VisibleForTesting
    public int getCurrentDisclosureCount() {
        return mDisclosureCount.get();
    }

    /** Get a singleton CellularIdentifierDisclosureNotifier. */
    public static synchronized CellularIdentifierDisclosureNotifier getInstance() {
        if (sInstance == null) {
@@ -168,64 +166,196 @@ public class CellularIdentifierDisclosureNotifier {
        return sInstance;
    }

    private Runnable closeWindow() {
    private Runnable incrementAndNotify(int subId) {
        return () -> {
            Rlog.i(TAG,
                    "Disclosure window closing. Disclosure count was " + mDisclosureCount.get());
            mDisclosureCount.set(0);
        };
            DisclosureWindow window = mWindows.get(subId);
            if (window == null) {
                window = new DisclosureWindow(subId);
                mWindows.put(subId, window);
            }

    private Runnable incrementAndNotify() {
        return () -> {
            int newCount = mDisclosureCount.incrementAndGet();
            Rlog.d(TAG, "Emitting notification. New disclosure count " + newCount);
            window.increment(this);

            // To reset the timer for our window, we first cancel an existing timer.
            boolean cancelled = cancelWindowCloseFuture();
            Rlog.d(TAG, "Result of attempting to cancel window closing future: " + cancelled);
            int disclosureCount = window.getDisclosureCount();

            try {
                mWhenWindowCloses =
                        mSerializedWorkQueue.schedule(
                                closeWindow(), mWindowCloseDuration, mWindowCloseUnit);
            } catch (RejectedExecutionException e) {
                Rlog.e(TAG, "Failed to schedule closeWindow: " + e.getMessage());
            }
            Rlog.d(
                    TAG,
                    "Emitting notification for subId: "
                            + subId
                            + ". New disclosure count "
                            + disclosureCount);

            // TODO (b/308985417) emit safety center issue
            //            mSafetySource.setIdentifierDisclosure(
            //                    subId,
            //                    disclosureCount,
            //                    window.getFirstOpen(),
            //                    window.getCurrentEnd());
        };
    }

    private Runnable onDisableNotifier() {
        return () -> {
            mDisclosureCount.set(0);
            cancelWindowCloseFuture();
            Rlog.d(TAG, "On disable notifier");
            for (DisclosureWindow window : mWindows.values()) {
                window.close();
            }
            // TODO (b/308985417) disable safety center issues
            // mSafetySource.setIdentifierDisclosureIssueEnabled(false);
        };
    }

    private Runnable onEnableNotifier() {
        return () -> {
            Rlog.i(TAG, "On enable notifier");
            // TODO (b/308985417) enable safety center issues
            // mSafetySource.setIdentifierDisclosureIssueEnabled(true);
        };
    }

    /**
     * A helper to cancel the Future that is in charge of closing the disclosure window. This must
     * only be called from within the single-threaded executor. Calling this method leaves a
     * completed or cancelled future in mWhenWindowCloses.
     *
     * @return boolean indicating whether or not the Future was actually cancelled. If false, this
     * likely indicates that the disclosure window has already closed.
     * Get the disclosure count for a given subId. NOTE: This method is not thread safe. Without
     * external synchronization, one should only call it if there are no pending tasks on the
     * Executor passed into this class.
     */
    @VisibleForTesting
    public int getCurrentDisclosureCount(int subId) {
        DisclosureWindow window = mWindows.get(subId);
        if (window != null) {
            return window.getDisclosureCount();
        }

        return 0;
    }

    /**
     * Get the open time for a given subId. NOTE: This method is not thread safe. Without
     * external synchronization, one should only call it if there are no pending tasks on the
     * Executor passed into this class.
     */
    @VisibleForTesting
    public Instant getFirstOpen(int subId) {
        DisclosureWindow window = mWindows.get(subId);
        if (window != null) {
            return window.getFirstOpen();
        }

        return null;
    }

    /**
     * Get the current end time for a given subId. NOTE: This method is not thread safe. Without
     * external synchronization, one should only call it if there are no pending tasks on the
     * Executor passed into this class.
     */
    @VisibleForTesting
    public Instant getCurrentEnd(int subId) {
        DisclosureWindow window = mWindows.get(subId);
        if (window != null) {
            return window.getCurrentEnd();
        }

        return null;
    }

    /**
     * A helper class that maintains all state associated with the disclosure window for a single
     * subId. No methods are thread safe. Callers must implement all synchronization.
     */
    private static class DisclosureWindow {
        private int mDisclosureCount;
        private Instant mWindowFirstOpen;
        private Instant mLastEvent;
        private ScheduledFuture<?> mWhenWindowCloses;

        private int mSubId;

        DisclosureWindow(int subId) {
            mDisclosureCount = 0;
            mWindowFirstOpen = null;
            mLastEvent = null;
            mSubId = subId;
            mWhenWindowCloses = null;
        }

        void increment(CellularIdentifierDisclosureNotifier notifier) {

            mDisclosureCount++;

            Instant now = Instant.now();
            if (mDisclosureCount == 1) {
                // Our window was opened for the first time
                mWindowFirstOpen = now;
            }

            mLastEvent = now;

            cancelWindowCloseFuture();

            try {
                mWhenWindowCloses =
                        notifier.mSerializedWorkQueue.schedule(
                                closeWindowRunnable(),
                                notifier.mWindowCloseDuration,
                                notifier.mWindowCloseUnit);
            } catch (RejectedExecutionException e) {
                Rlog.e(
                        TAG,
                        "Failed to schedule closeWindow for subId "
                                + mSubId
                                + " :  "
                                + e.getMessage());
            }
        }

        int getDisclosureCount() {
            return mDisclosureCount;
        }

        Instant getFirstOpen() {
            return mWindowFirstOpen;
        }

        Instant getCurrentEnd() {
            return mLastEvent;
        }

        void close() {
            mDisclosureCount = 0;
            mWindowFirstOpen = null;
            mLastEvent = null;

            if (mWhenWindowCloses == null) {
                return;
            }
            mWhenWindowCloses = null;
        }

        private Runnable closeWindowRunnable() {
            return () -> {
                Rlog.i(
                        TAG,
                        "Disclosure window closing for subId "
                                + mSubId
                                + ". Disclosure count was "
                                + getDisclosureCount());
                close();

                // TODO (b/308985417) clear safety center issue
                // mSafetySource.setIdentifierDisclosure(mSubId, 0, null, null);
            };
        }

        private boolean cancelWindowCloseFuture() {
            if (mWhenWindowCloses == null) {
                return false;
            }

        // While we choose not to interrupt a running Future (we pass `false` to the `cancel`
        // call), we shouldn't ever actually need this functionality because all the work on the
        // queue is serialized on a single thread. Nothing about the `closeWindow` call is ready
        // to handle interrupts, though, so this seems like a safer choice.
            // Pass false to not interrupt a running Future. Nothing about our notifier is ready
            // for this type of preemption.
            return mWhenWindowCloses.cancel(false);
        }

    }
}
+3 −2
Original line number Diff line number Diff line
@@ -2861,7 +2861,8 @@ public class GsmCdmaPhoneTest extends TelephonyTest {
                        new AsyncResult(null, disclosure, null)));
        processAllMessages();

        verify(mIdentifierDisclosureNotifier, times(1)).addDisclosure(eq(disclosure));
        verify(mIdentifierDisclosureNotifier, times(1))
                .addDisclosure(eq(mPhoneUT.getSubId()), eq(disclosure));
    }

    @Test
@@ -2886,7 +2887,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest {
        processAllMessages();

        verify(mIdentifierDisclosureNotifier, never())
                .addDisclosure(any(CellularIdentifierDisclosure.class));
                .addDisclosure(eq(mPhoneUT.getSubId()), any(CellularIdentifierDisclosure.class));
    }

    @Test
+78 −20
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.internal.telephony.security;

import static org.junit.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.telephony.CellularIdentifierDisclosure;

@@ -33,6 +35,8 @@ public class CellularIdentifierDisclosureNotifierTest {
    // 15 minutes and 100 milliseconds. Can be used to advance time in a test executor far enough
    // to (hopefully, if the code is behaving) close a disclosure window.
    private static final long WINDOW_CLOSE_ADVANCE_MILLIS = (15 * 60 * 1000) + 100;
    private static final int SUB_ID_1 = 1;
    private static final int SUB_ID_2 = 2;
    private CellularIdentifierDisclosure mDislosure;

    @Before
@@ -61,8 +65,8 @@ public class CellularIdentifierDisclosureNotifierTest {
                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);

        assertFalse(notifier.isEnabled());
        notifier.addDisclosure(mDislosure);
        assertEquals(0, notifier.getCurrentDisclosureCount());
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
    }

    @Test
@@ -78,9 +82,9 @@ public class CellularIdentifierDisclosureNotifierTest {
                        true);

        notifier.enable();
        notifier.addDisclosure(emergencyDisclosure);
        notifier.addDisclosure(SUB_ID_1, emergencyDisclosure);

        assertEquals(0, notifier.getCurrentDisclosureCount());
        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
    }

    @Test
@@ -90,11 +94,46 @@ public class CellularIdentifierDisclosureNotifierTest {
                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);

        notifier.enable();
        notifier.addDisclosure(mDislosure);
        notifier.addDisclosure(mDislosure);
        notifier.addDisclosure(mDislosure);

        assertEquals(3, notifier.getCurrentDisclosureCount());
        for (int i = 0; i < 3; i++) {
            notifier.addDisclosure(SUB_ID_1, mDislosure);
        }

        assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
    }

    @Test
    public void testSingleDisclosureStartAndEndTimesAreEqual() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);

        notifier.enable();

        notifier.addDisclosure(SUB_ID_1, mDislosure);

        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
        assertTrue(notifier.getFirstOpen(SUB_ID_1).equals(notifier.getCurrentEnd(SUB_ID_1)));
    }

    @Test
    public void testMultipleDisclosuresTimeWindows() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);

        notifier.enable();

        notifier.addDisclosure(SUB_ID_1, mDislosure);
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        notifier.addDisclosure(SUB_ID_1, mDislosure);

        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
        assertTrue(notifier.getFirstOpen(SUB_ID_1).isBefore(notifier.getCurrentEnd(SUB_ID_1)));
    }

    @Test
@@ -105,17 +144,17 @@ public class CellularIdentifierDisclosureNotifierTest {

        // One round of disclosures
        notifier.enable();
        notifier.addDisclosure(mDislosure);
        notifier.addDisclosure(mDislosure);
        assertEquals(2, notifier.getCurrentDisclosureCount());
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));

        // Window close should reset the counter
        executor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
        assertEquals(0, notifier.getCurrentDisclosureCount());
        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));

        // A new disclosure should increment as normal
        notifier.addDisclosure(mDislosure);
        assertEquals(1, notifier.getCurrentDisclosureCount());
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
    }

    @Test
@@ -126,15 +165,34 @@ public class CellularIdentifierDisclosureNotifierTest {

        // One round of disclosures
        notifier.enable();
        notifier.addDisclosure(mDislosure);
        notifier.addDisclosure(mDislosure);
        assertEquals(2, notifier.getCurrentDisclosureCount());
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));

        notifier.disable();
        assertFalse(notifier.isEnabled());

        // We're disabled now so no disclosures should open the disclosure window
        notifier.addDisclosure(mDislosure);
        assertEquals(0, notifier.getCurrentDisclosureCount());
        notifier.addDisclosure(SUB_ID_1, mDislosure);
        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
    }

    @Test
    public void testMultipleSubIdsTrackedIndependently() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);

        notifier.enable();
        for (int i = 0; i < 3; i++) {
            notifier.addDisclosure(SUB_ID_1, mDislosure);
        }

        for (int i = 0; i < 4; i++) {
            notifier.addDisclosure(SUB_ID_2, mDislosure);
        }

        assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
        assertEquals(4, notifier.getCurrentDisclosureCount(SUB_ID_2));
    }
}