Loading core/java/android/view/contentcapture/MainContentCaptureSession.java +92 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 = () -> { Loading @@ -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 { Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -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."); Loading @@ -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, " Loading Loading @@ -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 " Loading @@ -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)); Loading @@ -651,6 +718,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; mHandler.removeMessages(MSG_FLUSH); } Loading Loading @@ -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; } } core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java 0 → 100644 +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); } } Loading
core/java/android/view/contentcapture/MainContentCaptureSession.java +92 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 = () -> { Loading @@ -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 { Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -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."); Loading @@ -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, " Loading Loading @@ -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 " Loading @@ -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)); Loading @@ -651,6 +718,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; mHandler.removeMessages(MSG_FLUSH); } Loading Loading @@ -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; } }
core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java 0 → 100644 +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); } }