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

Commit 45f2889c authored by Nino Jagar's avatar Nino Jagar Committed by Automerger Merge Worker
Browse files

Merge "Connect main CC session with the protection flow" into udc-qpr-dev am: 34f72df7

parents 4b44d8d3 34f72df7
Loading
Loading
Loading
Loading
+92 −14
Original line number Diff line number Diff line
@@ -52,8 +52,10 @@ import android.util.Log;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;

import java.io.PrintWriter;
@@ -118,9 +120,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
    /**
     * Direct interface to the service binder object - it's used to send the events, including the
     * last ones (when the session is finished)
     *
     * @hide
     */
    @NonNull
    private IContentCaptureDirectManager mDirectServiceInterface;
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @Nullable
    public IContentCaptureDirectManager mDirectServiceInterface;

    @Nullable
    private DeathRecipient mDirectServiceVulture;

@@ -131,14 +137,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
    @Nullable
    private IBinder mShareableActivityToken;

    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @Nullable
    private ComponentName mComponentName;
    public ComponentName mComponentName;

    /**
     * List of events held to be sent as a batch.
     *
     * @hide
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @Nullable
    private ArrayList<ContentCaptureEvent> mEvents;
    public ArrayList<ContentCaptureEvent> mEvents;

    // Used just for debugging purposes (on dump)
    private long mNextFlush;
@@ -157,6 +168,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
    @NonNull
    private final SessionStateReceiver mSessionStateReceiver;

    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @Nullable
    public ContentProtectionEventProcessor mContentProtectionEventProcessor;

    private static class SessionStateReceiver extends IResultReceiver.Stub {
        private final WeakReference<MainContentCaptureSession> mMainSession;

@@ -194,8 +210,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
        }
    }

    protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
            @NonNull ContentCaptureManager manager, @NonNull Handler handler,
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
    public MainContentCaptureSession(
            @NonNull ContentCaptureManager.StrippedContext context,
            @NonNull ContentCaptureManager manager,
            @NonNull Handler handler,
            @NonNull IContentCaptureManager systemServerInterface) {
        mContext = context;
        mManager = manager;
@@ -273,15 +293,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
    }

    /**
     * Callback from {@code system_server} after call to
     * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
     * IResultReceiver)}.
     * Callback from {@code system_server} after call to {@link
     * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
     *
     * @param resultCode session state
     * @param binder handle to {@code IContentCaptureDirectManager}
     * @hide
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @UiThread
    private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
    public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
        if (binder != null) {
            mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
            mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            }
        }

        // Should not be possible for mComponentName to be null here but check anyway
        if (mManager.mOptions.contentProtectionOptions.enableReceiver
                && mManager.getContentProtectionEventBuffer() != null
                && mComponentName != null) {
            mContentProtectionEventProcessor =
                    new ContentProtectionEventProcessor(
                            mManager.getContentProtectionEventBuffer(),
                            mHandler,
                            mSystemServerInterface,
                            mComponentName.getPackageName());
        } else {
            mContentProtectionEventProcessor = null;
        }

        if ((resultCode & STATE_DISABLED) != 0) {
            resetSession(resultCode);
        } else {
@@ -311,8 +346,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
        }
    }

    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @UiThread
    private void sendEvent(@NonNull ContentCaptureEvent event) {
    public void sendEvent(@NonNull ContentCaptureEvent event) {
        sendEvent(event, /* forceFlush= */ false);
    }

@@ -337,6 +374,25 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
            return;
        }

        if (isContentProtectionReceiverEnabled()) {
            sendContentProtectionEvent(event);
        }
        if (isContentCaptureReceiverEnabled()) {
            sendContentCaptureEvent(event, forceFlush);
        }
    }

    @UiThread
    private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
        if (mContentProtectionEventProcessor != null) {
            mContentProtectionEventProcessor.processEvent(event);
        }
    }

    @UiThread
    private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
        final int eventType = event.getType();
        final int maxBufferSize = mManager.mOptions.maxBufferSize;
        if (mEvents == null) {
            if (sVerbose) {
@@ -528,9 +584,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
        flush(reason);
    }

    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    @Override
    @UiThread
    void flush(@FlushReason int reason) {
    public void flush(@FlushReason int reason) {
        if (mEvents == null || mEvents.size() == 0) {
            if (sVerbose) {
                Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            return;
        }

        if (!isContentCaptureReceiverEnabled()) {
            return;
        }

        if (mDirectServiceInterface == null) {
            if (sVerbose) {
                Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
        return new ParceledListSlice<>(events);
    }

    /** hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @UiThread
    private void destroySession() {
    public void destroySession() {
        if (sDebug) {
            Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
                    + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
        }
        mDirectServiceInterface = null;
        mContentProtectionEventProcessor = null;
    }

    // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
    // clearings out.
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @UiThread
    private void resetSession(int newState) {
    public void resetSession(int newState) {
        if (sVerbose) {
            Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
                    + getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            }
        }
        mDirectServiceInterface = null;
        mContentProtectionEventProcessor = null;
        mHandler.removeMessages(MSG_FLUSH);
    }

@@ -878,4 +946,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
    private String getDebugState(@FlushReason int reason) {
        return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
    }

    @UiThread
    private boolean isContentProtectionReceiverEnabled() {
        return mManager.mOptions.contentProtectionOptions.enableReceiver;
    }

    @UiThread
    private boolean isContentCaptureReceiverEnabled() {
        return mManager.mOptions.enableReceiver;
    }
}
+360 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view.contentcapture;

import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;

import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.Looper;
import android.view.contentprotection.ContentProtectionEventProcessor;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

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;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Test for {@link MainContentCaptureSession}.
 *
 * <p>Run with: {@code atest
 * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MainContentCaptureSessionTest {

    private static final int BUFFER_SIZE = 100;

    private static final int REASON = 123;

    private static final ContentCaptureEvent EVENT =
            new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);

    private static final ComponentName COMPONENT_NAME =
            new ComponentName("com.test.package", "TestClass");

    private static final Context sContext = ApplicationProvider.getApplicationContext();

    private static final ContentCaptureManager.StrippedContext sStrippedContext =
            new ContentCaptureManager.StrippedContext(sContext);

    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock private IContentCaptureManager mMockSystemServerInterface;

    @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;

    @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;

    @Test
    public void onSessionStarted_contentProtectionEnabled_processorCreated() {
        MainContentCaptureSession session = createSession();
        assertThat(session.mContentProtectionEventProcessor).isNull();

        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);

        assertThat(session.mContentProtectionEventProcessor).isNotNull();
    }

    @Test
    public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ true,
                        /* enableContentProtectionReceiver= */ false);
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);

        assertThat(session.mContentProtectionEventProcessor).isNull();
        verifyZeroInteractions(mMockContentProtectionEventProcessor);
    }

    @Test
    public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
        ContentCaptureOptions options =
                createOptions(
                        /* enableContentCaptureReceiver= */ true,
                        new ContentCaptureOptions.ContentProtectionOptions(
                                /* enableReceiver= */ true, -BUFFER_SIZE));
        MainContentCaptureSession session = createSession(options);
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);

        assertThat(session.mContentProtectionEventProcessor).isNull();
        verifyZeroInteractions(mMockContentProtectionEventProcessor);
    }

    @Test
    public void onSessionStarted_noComponentName_processorNotCreated() {
        MainContentCaptureSession session = createSession();
        session.mComponentName = null;

        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);

        assertThat(session.mContentProtectionEventProcessor).isNull();
    }

    @Test
    public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ false,
                        /* enableContentProtectionReceiver= */ false);
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.sendEvent(EVENT);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mEvents).isNull();
    }

    @Test
    public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ false,
                        /* enableContentProtectionReceiver= */ true);
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.sendEvent(EVENT);

        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
        assertThat(session.mEvents).isNull();
    }

    @Test
    public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ true,
                        /* enableContentProtectionReceiver= */ false);
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.sendEvent(EVENT);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mEvents).isNotNull();
        assertThat(session.mEvents).containsExactly(EVENT);
    }

    @Test
    public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
        MainContentCaptureSession session = createSession();
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.sendEvent(EVENT);

        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
        assertThat(session.mEvents).isNotNull();
        assertThat(session.mEvents).containsExactly(EVENT);
    }

    @Test
    public void sendEvent_contentProtectionEnabled_processorNotCreated() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ false,
                        /* enableContentProtectionReceiver= */ true);

        session.sendEvent(EVENT);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mEvents).isNull();
    }

    @Test
    public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
        ContentCaptureOptions options =
                createOptions(
                        /* enableContentCaptureReceiver= */ false,
                        /* enableContentProtectionReceiver= */ false);
        MainContentCaptureSession session = createSession(options);
        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
        session.mDirectServiceInterface = mMockContentCaptureDirectManager;

        session.flush(REASON);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        verifyZeroInteractions(mMockContentCaptureDirectManager);
        assertThat(session.mEvents).containsExactly(EVENT);
    }

    @Test
    public void flush_contentCaptureDisabled_contentProtectionEnabled() {
        MainContentCaptureSession session =
                createSession(
                        /* enableContentCaptureReceiver= */ false,
                        /* enableContentProtectionReceiver= */ true);
        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
        session.mDirectServiceInterface = mMockContentCaptureDirectManager;

        session.flush(REASON);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        verifyZeroInteractions(mMockContentCaptureDirectManager);
        assertThat(session.mEvents).containsExactly(EVENT);
    }

    @Test
    public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
        ContentCaptureOptions options =
                createOptions(
                        /* enableContentCaptureReceiver= */ true,
                        /* enableContentProtectionReceiver= */ false);
        MainContentCaptureSession session = createSession(options);
        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
        session.mDirectServiceInterface = mMockContentCaptureDirectManager;

        session.flush(REASON);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mEvents).isEmpty();
        assertEventFlushedContentCapture(options);
    }

    @Test
    public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
        ContentCaptureOptions options =
                createOptions(
                        /* enableContentCaptureReceiver= */ true,
                        /* enableContentProtectionReceiver= */ true);
        MainContentCaptureSession session = createSession(options);
        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
        session.mDirectServiceInterface = mMockContentCaptureDirectManager;

        session.flush(REASON);

        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mEvents).isEmpty();
        assertEventFlushedContentCapture(options);
    }

    @Test
    public void destroySession() throws Exception {
        MainContentCaptureSession session = createSession();
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.destroySession();

        verify(mMockSystemServerInterface).finishSession(anyInt());
        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mDirectServiceInterface).isNull();
        assertThat(session.mContentProtectionEventProcessor).isNull();
    }

    @Test
    public void resetSession() {
        MainContentCaptureSession session = createSession();
        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;

        session.resetSession(/* newState= */ 0);

        verifyZeroInteractions(mMockSystemServerInterface);
        verifyZeroInteractions(mMockContentProtectionEventProcessor);
        assertThat(session.mDirectServiceInterface).isNull();
        assertThat(session.mContentProtectionEventProcessor).isNull();
    }

    private static ContentCaptureOptions createOptions(
            boolean enableContentCaptureReceiver,
            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
        return new ContentCaptureOptions(
                /* loggingLevel= */ 0,
                BUFFER_SIZE,
                /* idleFlushingFrequencyMs= */ 0,
                /* textChangeFlushingFrequencyMs= */ 0,
                /* logHistorySize= */ 0,
                /* disableFlushForViewTreeAppearing= */ false,
                enableContentCaptureReceiver,
                contentProtectionOptions,
                /* whitelistedComponents= */ null);
    }

    private static ContentCaptureOptions createOptions(
            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
        return createOptions(
                enableContentCaptureReceiver,
                new ContentCaptureOptions.ContentProtectionOptions(
                        enableContentProtectionReceiver, BUFFER_SIZE));
    }

    private ContentCaptureManager createManager(ContentCaptureOptions options) {
        return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
    }

    private MainContentCaptureSession createSession(ContentCaptureManager manager) {
        MainContentCaptureSession session =
                new MainContentCaptureSession(
                        sStrippedContext,
                        manager,
                        new Handler(Looper.getMainLooper()),
                        mMockSystemServerInterface);
        session.mComponentName = COMPONENT_NAME;
        return session;
    }

    private MainContentCaptureSession createSession(ContentCaptureOptions options) {
        return createSession(createManager(options));
    }

    private MainContentCaptureSession createSession(
            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
        return createSession(
                createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
    }

    private MainContentCaptureSession createSession() {
        return createSession(
                /* enableContentCaptureReceiver= */ true,
                /* enableContentProtectionReceiver= */ true);
    }

    private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
        ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
        verify(mMockContentCaptureDirectManager)
                .sendEvents(captor.capture(), eq(REASON), eq(options));

        assertThat(captor.getValue()).isNotNull();
        List<ContentCaptureEvent> actual = captor.getValue().getList();
        assertThat(actual).isNotNull();
        assertThat(actual).containsExactly(EVENT);
    }
}