Loading core/java/android/service/contentcapture/ContentCaptureServiceInfo.java +1 −1 Original line number Diff line number Diff line Loading @@ -48,7 +48,7 @@ import java.io.PrintWriter; * * @hide */ public final class ContentCaptureServiceInfo { public class ContentCaptureServiceInfo { private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName(); private static final String XML_TAG_SERVICE = "content-capture-service"; Loading core/java/android/view/contentcapture/IContentCaptureManager.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.view.contentcapture; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.DataRemovalRequest; Loading Loading @@ -108,4 +109,9 @@ oneway interface IContentCaptureManager { */ void registerContentCaptureOptionsCallback(String packageName, in IContentCaptureOptionsCallback callback); /** * Notifies the system server that a login was detected. */ void onLoginDetected(in ParceledListSlice<ContentCaptureEvent> events); } core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +1 −1 Original line number Diff line number Diff line Loading @@ -162,7 +162,7 @@ public class ContentProtectionEventProcessor { private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { try { // TODO(b/275732576): Call mContentCaptureManager mContentCaptureManager.onLoginDetected(events); } catch (Exception ex) { Log.e(TAG, "Failed to flush events for: " + mPackageName, ex); } Loading core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +48 −9 Original line number Diff line number Diff line Loading @@ -26,10 +26,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Handler; import android.os.Looper; import android.text.InputType; Loading @@ -51,6 +53,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; Loading Loading @@ -82,6 +85,9 @@ public class ContentProtectionEventProcessorTest { private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); private static final ContentCaptureEvent[] BUFFERED_EVENTS = new ContentCaptureEvent[] {PROCESS_EVENT}; private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); Loading Loading @@ -165,11 +171,12 @@ public class ContentProtectionEventProcessorTest { verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void processEvent_loginDetected() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_loginDetected() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -179,6 +186,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test Loading @@ -192,6 +200,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -205,6 +214,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -218,11 +228,12 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void processEvent_multipleLoginsDetected_belowFlushThreshold() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -236,11 +247,12 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test public void processEvent_multipleLoginsDetected_aboveFlushThreshold() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -256,6 +268,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, times(2)).clear(); verify(mMockEventBuffer, times(2)).toArray(); assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); } @Test Loading @@ -269,6 +282,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -282,6 +296,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -296,6 +311,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -309,21 +325,22 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void isPasswordField_webView() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void isPasswordField_webView() throws Exception { ContentCaptureEvent event = createWebViewPasswordFieldEvent( /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(event, /* times= */ 1); } @Test Loading @@ -337,6 +354,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -350,6 +368,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -362,6 +381,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -373,6 +393,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -384,6 +405,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -395,6 +417,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -406,6 +429,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -420,6 +444,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } private static ContentCaptureEvent createEvent(int type) { Loading Loading @@ -474,4 +499,18 @@ public class ContentProtectionEventProcessorTest { return createProcessEvent( /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); } private void assertOnLoginDetected() throws Exception { assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); } private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception { ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); assertThat(captor.getValue()).isNotNull(); List<ContentCaptureEvent> actual = captor.getValue().getList(); assertThat(actual).isNotNull(); assertThat(actual).containsExactly(event); } } services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +106 −7 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import android.content.Context; import android.content.pm.ActivityPresentationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Binder; Loading @@ -69,6 +70,7 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.ContentCaptureServiceInfo; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.IDataShareReadAdapter; import android.service.voice.VoiceInteractionManagerInternal; Loading @@ -79,6 +81,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureHelper; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.DataRemovalRequest; Loading @@ -96,6 +99,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.contentprotection.ContentProtectionBlocklistManager; import com.android.server.contentprotection.ContentProtectionPackageManager; import com.android.server.contentprotection.RemoteContentProtectionService; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; Loading Loading @@ -208,6 +212,8 @@ public class ContentCaptureManagerService extends final GlobalContentCaptureOptions mGlobalContentCaptureOptions = new GlobalContentCaptureOptions(); @Nullable private final ComponentName mContentProtectionServiceComponentName; @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager; public ContentCaptureManagerService(@NonNull Context context) { Loading Loading @@ -249,12 +255,18 @@ public class ContentCaptureManagerService extends } if (getEnableContentProtectionReceiverLocked()) { mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context); mContentProtectionServiceComponentName = getContentProtectionServiceComponentName(); if (mContentProtectionServiceComponentName != null) { mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(); mContentProtectionBlocklistManager.updateBlocklist( mDevCfgContentProtectionAppsBlocklistSize); } else { mContentProtectionBlocklistManager = null; } } else { mContentProtectionServiceComponentName = null; mContentProtectionBlocklistManager = null; } } @Override // from AbstractMasterSystemService Loading Loading @@ -785,9 +797,82 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager( @NonNull Context context) { return new ContentProtectionBlocklistManager(new ContentProtectionPackageManager(context)); protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() { return new ContentProtectionBlocklistManager( new ContentProtectionPackageManager(getContext())); } @Nullable private ComponentName getContentProtectionServiceComponentName() { String flatComponentName = getContentProtectionServiceFlatComponentName(); ComponentName componentName = ComponentName.unflattenFromString(flatComponentName); if (componentName == null) { return null; } // Check permissions by trying to construct {@link ContentCaptureServiceInfo} try { createContentProtectionServiceInfo(componentName); } catch (Exception ex) { // Swallow, exception was already logged return null; } return componentName; } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @Nullable protected String getContentProtectionServiceFlatComponentName() { return getContext() .getString(com.android.internal.R.string.config_defaultContentProtectionService); } /** * Can also throw runtime exceptions such as {@link SecurityException}. * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentCaptureServiceInfo createContentProtectionServiceInfo( @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException { return new ContentCaptureServiceInfo( getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId()); } @Nullable private RemoteContentProtectionService createRemoteContentProtectionService() { if (mContentProtectionServiceComponentName == null) { // This case should not be possible but make sure return null; } synchronized (mLock) { if (!mDevCfgEnableContentProtectionReceiver) { return null; } } return createRemoteContentProtectionService(mContentProtectionServiceComponentName); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected RemoteContentProtectionService createRemoteContentProtectionService( @NonNull ComponentName componentName) { return new RemoteContentProtectionService( getContext(), componentName, UserHandle.getCallingUserId(), isBindInstantServiceAllowed()); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentCaptureManagerServiceStub getContentCaptureManagerServiceStub() { return mContentCaptureManagerServiceStub; } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { Loading Loading @@ -1023,6 +1108,19 @@ public class ContentCaptureManagerService extends public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) { ContentCaptureManagerService.this.setDefaultServiceEnabled(userId, enabled); } @Override public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { RemoteContentProtectionService service = createRemoteContentProtectionService(); if (service == null) { return; } try { service.onLoginDetected(events); } catch (Exception ex) { Slog.e(TAG, "Failed to call remote service", ex); } } } private final class LocalService extends ContentCaptureManagerInternal { Loading Loading @@ -1205,7 +1303,8 @@ public class ContentCaptureManagerService extends } private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) { if (mContentProtectionBlocklistManager == null) { if (mContentProtectionServiceComponentName == null || mContentProtectionBlocklistManager == null) { return false; } synchronized (mLock) { Loading Loading
core/java/android/service/contentcapture/ContentCaptureServiceInfo.java +1 −1 Original line number Diff line number Diff line Loading @@ -48,7 +48,7 @@ import java.io.PrintWriter; * * @hide */ public final class ContentCaptureServiceInfo { public class ContentCaptureServiceInfo { private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName(); private static final String XML_TAG_SERVICE = "content-capture-service"; Loading
core/java/android/view/contentcapture/IContentCaptureManager.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.view.contentcapture; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.DataRemovalRequest; Loading Loading @@ -108,4 +109,9 @@ oneway interface IContentCaptureManager { */ void registerContentCaptureOptionsCallback(String packageName, in IContentCaptureOptionsCallback callback); /** * Notifies the system server that a login was detected. */ void onLoginDetected(in ParceledListSlice<ContentCaptureEvent> events); }
core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +1 −1 Original line number Diff line number Diff line Loading @@ -162,7 +162,7 @@ public class ContentProtectionEventProcessor { private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { try { // TODO(b/275732576): Call mContentCaptureManager mContentCaptureManager.onLoginDetected(events); } catch (Exception ex) { Log.e(TAG, "Failed to flush events for: " + mPackageName, ex); } Loading
core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +48 −9 Original line number Diff line number Diff line Loading @@ -26,10 +26,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Handler; import android.os.Looper; import android.text.InputType; Loading @@ -51,6 +53,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; Loading Loading @@ -82,6 +85,9 @@ public class ContentProtectionEventProcessorTest { private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); private static final ContentCaptureEvent[] BUFFERED_EVENTS = new ContentCaptureEvent[] {PROCESS_EVENT}; private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); Loading Loading @@ -165,11 +171,12 @@ public class ContentProtectionEventProcessorTest { verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void processEvent_loginDetected() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_loginDetected() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -179,6 +186,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test Loading @@ -192,6 +200,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -205,6 +214,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -218,11 +228,12 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void processEvent_multipleLoginsDetected_belowFlushThreshold() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -236,11 +247,12 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(); } @Test public void processEvent_multipleLoginsDetected_aboveFlushThreshold() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); mContentProtectionEventProcessor.mPasswordFieldDetected = true; mContentProtectionEventProcessor.mSuspiciousTextDetected = true; Loading @@ -256,6 +268,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, times(2)).clear(); verify(mMockEventBuffer, times(2)).toArray(); assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); } @Test Loading @@ -269,6 +282,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -282,6 +296,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -296,6 +311,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -309,21 +325,22 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test public void isPasswordField_webView() { when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]); public void isPasswordField_webView() throws Exception { ContentCaptureEvent event = createWebViewPasswordFieldEvent( /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); mContentProtectionEventProcessor.processEvent(event); assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); assertOnLoginDetected(event, /* times= */ 1); } @Test Loading @@ -337,6 +354,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -350,6 +368,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -362,6 +381,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -373,6 +393,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -384,6 +405,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -395,6 +417,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -406,6 +429,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } @Test Loading @@ -420,6 +444,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); verifyZeroInteractions(mMockContentCaptureManager); } private static ContentCaptureEvent createEvent(int type) { Loading Loading @@ -474,4 +499,18 @@ public class ContentProtectionEventProcessorTest { return createProcessEvent( /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); } private void assertOnLoginDetected() throws Exception { assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); } private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception { ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); assertThat(captor.getValue()).isNotNull(); List<ContentCaptureEvent> actual = captor.getValue().getList(); assertThat(actual).isNotNull(); assertThat(actual).containsExactly(event); } }
services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +106 −7 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import android.content.Context; import android.content.pm.ActivityPresentationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Binder; Loading @@ -69,6 +70,7 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.ContentCaptureServiceInfo; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.IDataShareReadAdapter; import android.service.voice.VoiceInteractionManagerInternal; Loading @@ -79,6 +81,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureHelper; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.DataRemovalRequest; Loading @@ -96,6 +99,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.contentprotection.ContentProtectionBlocklistManager; import com.android.server.contentprotection.ContentProtectionPackageManager; import com.android.server.contentprotection.RemoteContentProtectionService; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; Loading Loading @@ -208,6 +212,8 @@ public class ContentCaptureManagerService extends final GlobalContentCaptureOptions mGlobalContentCaptureOptions = new GlobalContentCaptureOptions(); @Nullable private final ComponentName mContentProtectionServiceComponentName; @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager; public ContentCaptureManagerService(@NonNull Context context) { Loading Loading @@ -249,12 +255,18 @@ public class ContentCaptureManagerService extends } if (getEnableContentProtectionReceiverLocked()) { mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context); mContentProtectionServiceComponentName = getContentProtectionServiceComponentName(); if (mContentProtectionServiceComponentName != null) { mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(); mContentProtectionBlocklistManager.updateBlocklist( mDevCfgContentProtectionAppsBlocklistSize); } else { mContentProtectionBlocklistManager = null; } } else { mContentProtectionServiceComponentName = null; mContentProtectionBlocklistManager = null; } } @Override // from AbstractMasterSystemService Loading Loading @@ -785,9 +797,82 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager( @NonNull Context context) { return new ContentProtectionBlocklistManager(new ContentProtectionPackageManager(context)); protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() { return new ContentProtectionBlocklistManager( new ContentProtectionPackageManager(getContext())); } @Nullable private ComponentName getContentProtectionServiceComponentName() { String flatComponentName = getContentProtectionServiceFlatComponentName(); ComponentName componentName = ComponentName.unflattenFromString(flatComponentName); if (componentName == null) { return null; } // Check permissions by trying to construct {@link ContentCaptureServiceInfo} try { createContentProtectionServiceInfo(componentName); } catch (Exception ex) { // Swallow, exception was already logged return null; } return componentName; } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @Nullable protected String getContentProtectionServiceFlatComponentName() { return getContext() .getString(com.android.internal.R.string.config_defaultContentProtectionService); } /** * Can also throw runtime exceptions such as {@link SecurityException}. * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentCaptureServiceInfo createContentProtectionServiceInfo( @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException { return new ContentCaptureServiceInfo( getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId()); } @Nullable private RemoteContentProtectionService createRemoteContentProtectionService() { if (mContentProtectionServiceComponentName == null) { // This case should not be possible but make sure return null; } synchronized (mLock) { if (!mDevCfgEnableContentProtectionReceiver) { return null; } } return createRemoteContentProtectionService(mContentProtectionServiceComponentName); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected RemoteContentProtectionService createRemoteContentProtectionService( @NonNull ComponentName componentName) { return new RemoteContentProtectionService( getContext(), componentName, UserHandle.getCallingUserId(), isBindInstantServiceAllowed()); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected ContentCaptureManagerServiceStub getContentCaptureManagerServiceStub() { return mContentCaptureManagerServiceStub; } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { Loading Loading @@ -1023,6 +1108,19 @@ public class ContentCaptureManagerService extends public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) { ContentCaptureManagerService.this.setDefaultServiceEnabled(userId, enabled); } @Override public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { RemoteContentProtectionService service = createRemoteContentProtectionService(); if (service == null) { return; } try { service.onLoginDetected(events); } catch (Exception ex) { Slog.e(TAG, "Failed to call remote service", ex); } } } private final class LocalService extends ContentCaptureManagerInternal { Loading Loading @@ -1205,7 +1303,8 @@ public class ContentCaptureManagerService extends } private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) { if (mContentProtectionBlocklistManager == null) { if (mContentProtectionServiceComponentName == null || mContentProtectionBlocklistManager == null) { return false; } synchronized (mLock) { Loading