diff --git a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java index fb6061916d99d..4aa5c00a6a522 100644 --- a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java +++ b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java @@ -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"; diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index a64111069c9b9..14879977d2a50 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -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; @@ -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 events); } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index 0840b66e5d1e4..e49df21692c58 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -162,7 +162,7 @@ public class ContentProtectionEventProcessor { private void handlerOnLoginDetected(@NonNull ParceledListSlice events) { try { - // TODO(b/275732576): Call mContentCaptureManager + mContentCaptureManager.onLoginDetected(events); } catch (Exception ex) { Log.e(TAG, "Failed to flush events for: " + mPackageName, ex); } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index fd5627da6d396..4adadf11903ee 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -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; @@ -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; @@ -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 EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); @@ -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; @@ -179,6 +186,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer).clear(); verify(mMockEventBuffer).toArray(); + assertOnLoginDetected(); } @Test @@ -192,6 +200,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -205,6 +214,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -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; @@ -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; @@ -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 @@ -269,6 +282,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -282,6 +296,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -296,6 +311,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -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 @@ -337,6 +354,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -350,6 +368,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -362,6 +381,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -373,6 +393,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -384,6 +405,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -395,6 +417,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -406,6 +429,7 @@ public class ContentProtectionEventProcessorTest { assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } @Test @@ -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) { @@ -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 captor = ArgumentCaptor.forClass(ParceledListSlice.class); + verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); + + assertThat(captor.getValue()).isNotNull(); + List actual = captor.getValue().getList(); + assertThat(actual).isNotNull(); + assertThat(actual).containsExactly(event); + } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 9ff0746ebd19d..cfd9f16541f24 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -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) { @@ -249,10 +255,16 @@ public class ContentCaptureManagerService extends } if (getEnableContentProtectionReceiverLocked()) { - mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context); - mContentProtectionBlocklistManager.updateBlocklist( - mDevCfgContentProtectionAppsBlocklistSize); + mContentProtectionServiceComponentName = getContentProtectionServiceComponentName(); + if (mContentProtectionServiceComponentName != null) { + mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(); + mContentProtectionBlocklistManager.updateBlocklist( + mDevCfgContentProtectionAppsBlocklistSize); + } else { + mContentProtectionBlocklistManager = null; + } } else { + mContentProtectionServiceComponentName = null; mContentProtectionBlocklistManager = null; } } @@ -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 { @@ -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 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 { @@ -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) { diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java index 7b34e737b58ca..f5e5a431e3dd0 100644 --- a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java +++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java @@ -37,7 +37,8 @@ import java.time.Duration; * * @hide */ -class RemoteContentProtectionService extends ServiceConnector.Impl { +public class RemoteContentProtectionService + extends ServiceConnector.Impl { private static final String TAG = RemoteContentProtectionService.class.getSimpleName(); @@ -45,7 +46,7 @@ class RemoteContentProtectionService extends ServiceConnector.Impl PARCELED_EVENTS = + new ParceledListSlice<>(ImmutableList.of(EVENT)); + private static final Context sContext = ApplicationProvider.getApplicationContext(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -76,10 +87,22 @@ public class ContentCaptureManagerServiceTest { @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager; + @Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo; + + @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService; + private boolean mDevCfgEnableContentProtectionReceiver; private int mContentProtectionBlocklistManagersCreated; + private int mContentProtectionServiceInfosCreated; + + private int mRemoteContentProtectionServicesCreated; + + private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString(); + + private boolean mContentProtectionServiceInfoConstructorShouldThrow; + private ContentCaptureManagerService mContentCaptureManagerService; @Before @@ -91,24 +114,54 @@ public class ContentCaptureManagerServiceTest { } @Test - public void constructor_default_doesNotCreateContentProtectionBlocklistManager() { + public void constructor_contentProtection_flagDisabled_noBlocklistManager() { assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0); + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verifyZeroInteractions(mMockContentProtectionBlocklistManager); } @Test - public void constructor_flagDisabled_doesNotContentProtectionBlocklistManager() { + public void constructor_contentProtection_componentNameNull_noBlocklistManager() { + mConfigDefaultContentProtectionService = null; + + mContentCaptureManagerService = new TestContentCaptureManagerService(); + assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0); + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verifyZeroInteractions(mMockContentProtectionBlocklistManager); } @Test - public void constructor_flagEnabled_createsContentProtectionBlocklistManager() { + public void constructor_contentProtection_componentNameBlank_noBlocklistManager() { + mConfigDefaultContentProtectionService = " "; + + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0); + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); + verifyZeroInteractions(mMockContentProtectionBlocklistManager); + } + + @Test + public void constructor_contentProtection_serviceInfoThrows_noBlocklistManager() { + mDevCfgEnableContentProtectionReceiver = true; + mContentProtectionServiceInfoConstructorShouldThrow = true; + + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0); + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1); + verifyZeroInteractions(mMockContentProtectionBlocklistManager); + } + + @Test + public void constructor_contentProtection_enabled_createsBlocklistManager() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1); + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1); verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt()); } @@ -298,6 +351,35 @@ public class ContentCaptureManagerServiceTest { verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString()); } + @Test + public void onLoginDetected_disabledAfterConstructor() { + mDevCfgEnableContentProtectionReceiver = true; + mContentCaptureManagerService = new TestContentCaptureManagerService(); + mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false; + + mContentCaptureManagerService + .getContentCaptureManagerServiceStub() + .onLoginDetected(PARCELED_EVENTS); + + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1); + assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0); + verifyZeroInteractions(mMockRemoteContentProtectionService); + } + + @Test + public void onLoginDetected_enabled() { + mDevCfgEnableContentProtectionReceiver = true; + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + mContentCaptureManagerService + .getContentCaptureManagerServiceStub() + .onLoginDetected(PARCELED_EVENTS); + + assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1); + assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(1); + verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS); + } + private class TestContentCaptureManagerService extends ContentCaptureManagerService { TestContentCaptureManagerService() { @@ -312,10 +394,31 @@ public class ContentCaptureManagerServiceTest { } @Override - protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager( - @NonNull Context context) { + protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() { mContentProtectionBlocklistManagersCreated++; return mMockContentProtectionBlocklistManager; } + + @Override + protected String getContentProtectionServiceFlatComponentName() { + return mConfigDefaultContentProtectionService; + } + + @Override + protected ContentCaptureServiceInfo createContentProtectionServiceInfo( + @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException { + mContentProtectionServiceInfosCreated++; + if (mContentProtectionServiceInfoConstructorShouldThrow) { + throw new RuntimeException("TEST RUNTIME EXCEPTION"); + } + return mMockContentCaptureServiceInfo; + } + + @Override + protected RemoteContentProtectionService createRemoteContentProtectionService( + @NonNull ComponentName componentName) { + mRemoteContentProtectionServicesCreated++; + return mMockRemoteContentProtectionService; + } } }