Loading src/java/com/android/internal/telephony/GsmCdmaPhone.java +9 −4 Original line number Diff line number Diff line Loading @@ -1842,8 +1842,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; } Loading Loading @@ -2850,7 +2853,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(); Loading Loading @@ -3710,7 +3715,7 @@ public class GsmCdmaPhone extends Phone { if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents() && mIdentifierDisclosureNotifier != null && disclosure != null) { mIdentifierDisclosureNotifier.addDisclosure(disclosure); mIdentifierDisclosureNotifier.addDisclosure(getSubId(), disclosure); } break; Loading src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java +182 −52 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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) { Loading @@ -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()); } Loading Loading @@ -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) { Loading @@ -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); } } } tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java +78 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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)); } } Loading
src/java/com/android/internal/telephony/GsmCdmaPhone.java +9 −4 Original line number Diff line number Diff line Loading @@ -1842,8 +1842,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; } Loading Loading @@ -2850,7 +2853,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(); Loading Loading @@ -3710,7 +3715,7 @@ public class GsmCdmaPhone extends Phone { if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents() && mIdentifierDisclosureNotifier != null && disclosure != null) { mIdentifierDisclosureNotifier.addDisclosure(disclosure); mIdentifierDisclosureNotifier.addDisclosure(getSubId(), disclosure); } break; Loading
src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java +182 −52 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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) { Loading @@ -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()); } Loading Loading @@ -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) { Loading @@ -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); } } }
tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading
tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java +78 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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)); } }