Loading core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +26 −0 Original line number Original line Diff line number Diff line Loading @@ -75,6 +75,8 @@ public class ContentProtectionEventProcessor { ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer; @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer; @NonNull private final Handler mHandler; @NonNull private final Handler mHandler; Loading @@ -93,6 +95,8 @@ public class ContentProtectionEventProcessor { @Nullable @Nullable public Instant mLastFlushTime; public Instant mLastFlushTime; private int mResetLoginRemainingEventsToProcess; public ContentProtectionEventProcessor( public ContentProtectionEventProcessor( @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull Handler handler, @NonNull Handler handler, Loading Loading @@ -130,6 +134,8 @@ public class ContentProtectionEventProcessor { mSuspiciousTextDetected |= isSuspiciousText(event); mSuspiciousTextDetected |= isSuspiciousText(event); if (mPasswordFieldDetected && mSuspiciousTextDetected) { if (mPasswordFieldDetected && mSuspiciousTextDetected) { loginDetected(); loginDetected(); } else { maybeResetLoginFlags(); } } } } Loading @@ -139,8 +145,28 @@ public class ContentProtectionEventProcessor { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { flush(); flush(); } } resetLoginFlags(); } @UiThread private void resetLoginFlags() { mPasswordFieldDetected = false; mPasswordFieldDetected = false; mSuspiciousTextDetected = false; mSuspiciousTextDetected = false; mResetLoginRemainingEventsToProcess = 0; } @UiThread private void maybeResetLoginFlags() { if (mPasswordFieldDetected || mSuspiciousTextDetected) { if (mResetLoginRemainingEventsToProcess <= 0) { mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; } else { mResetLoginRemainingEventsToProcess--; if (mResetLoginRemainingEventsToProcess <= 0) { resetLoginFlags(); } } } } } @UiThread @UiThread Loading core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +52 −0 Original line number Original line Diff line number Diff line Loading @@ -91,6 +91,8 @@ public class ContentProtectionEventProcessorTest { private static final Set<Integer> EVENT_TYPES_TO_STORE = private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer; @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer; Loading Loading @@ -231,6 +233,56 @@ public class ContentProtectionEventProcessorTest { verifyZeroInteractions(mMockContentCaptureManager); verifyZeroInteractions(mMockContentCaptureManager); } } @Test public void processEvent_loginDetected_belowResetLimit() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mSuspiciousTextDetected = true; ContentCaptureEvent event = createAndroidPasswordFieldEvent( ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); } assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test public void processEvent_loginDetected_aboveResetLimit() throws Exception { mContentProtectionEventProcessor.mSuspiciousTextDetected = true; ContentCaptureEvent event = createAndroidPasswordFieldEvent( ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); } assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); } @Test @Test public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); Loading Loading
core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +26 −0 Original line number Original line Diff line number Diff line Loading @@ -75,6 +75,8 @@ public class ContentProtectionEventProcessor { ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer; @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer; @NonNull private final Handler mHandler; @NonNull private final Handler mHandler; Loading @@ -93,6 +95,8 @@ public class ContentProtectionEventProcessor { @Nullable @Nullable public Instant mLastFlushTime; public Instant mLastFlushTime; private int mResetLoginRemainingEventsToProcess; public ContentProtectionEventProcessor( public ContentProtectionEventProcessor( @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull Handler handler, @NonNull Handler handler, Loading Loading @@ -130,6 +134,8 @@ public class ContentProtectionEventProcessor { mSuspiciousTextDetected |= isSuspiciousText(event); mSuspiciousTextDetected |= isSuspiciousText(event); if (mPasswordFieldDetected && mSuspiciousTextDetected) { if (mPasswordFieldDetected && mSuspiciousTextDetected) { loginDetected(); loginDetected(); } else { maybeResetLoginFlags(); } } } } Loading @@ -139,8 +145,28 @@ public class ContentProtectionEventProcessor { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { flush(); flush(); } } resetLoginFlags(); } @UiThread private void resetLoginFlags() { mPasswordFieldDetected = false; mPasswordFieldDetected = false; mSuspiciousTextDetected = false; mSuspiciousTextDetected = false; mResetLoginRemainingEventsToProcess = 0; } @UiThread private void maybeResetLoginFlags() { if (mPasswordFieldDetected || mSuspiciousTextDetected) { if (mResetLoginRemainingEventsToProcess <= 0) { mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; } else { mResetLoginRemainingEventsToProcess--; if (mResetLoginRemainingEventsToProcess <= 0) { resetLoginFlags(); } } } } } @UiThread @UiThread Loading
core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +52 −0 Original line number Original line Diff line number Diff line Loading @@ -91,6 +91,8 @@ public class ContentProtectionEventProcessorTest { private static final Set<Integer> EVENT_TYPES_TO_STORE = private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer; @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer; Loading Loading @@ -231,6 +233,56 @@ public class ContentProtectionEventProcessorTest { verifyZeroInteractions(mMockContentCaptureManager); verifyZeroInteractions(mMockContentCaptureManager); } } @Test public void processEvent_loginDetected_belowResetLimit() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mSuspiciousTextDetected = true; ContentCaptureEvent event = createAndroidPasswordFieldEvent( ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); } assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test public void processEvent_loginDetected_aboveResetLimit() throws Exception { mContentProtectionEventProcessor.mSuspiciousTextDetected = true; ContentCaptureEvent event = createAndroidPasswordFieldEvent( ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); } assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); } @Test @Test public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); Loading