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

Commit 2958d445 authored by Nino Jagar's avatar Nino Jagar
Browse files

Connect protection event processor with remote service

BYPASS_INCLUSIVE_LANGUAGE_REASON=Existing code in production

Bug: 275732576
Test: Added tests
Change-Id: I910460bb6fece38f38ffefa7eed4e8cad3f43277
parent 68587626
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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";
+6 −0
Original line number Diff line number Diff line
@@ -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<ContentCaptureEvent> events);
}
+1 −1
Original line number Diff line number Diff line
@@ -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);
        }
+48 −9
Original line number Diff line number Diff line
@@ -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<Integer> 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<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);
    }
}
+106 −7
Original line number Diff line number Diff line
@@ -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,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
@@ -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<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 {
@@ -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