Connect main CC session with the protection flow

BYPASS_INCLUSIVE_LANGUAGE_REASON=Existing code in production

Bug: 275732576
Test: Added tests

Change-Id: I5972b876c0c4451ac0ae05efae4fa8ea740038f7
This commit is contained in:
Nino Jagar
2023-06-01 21:19:17 +00:00
parent a86313f32c
commit 699deb9509
2 changed files with 452 additions and 14 deletions

View File

@@ -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;
}
}

View File

@@ -0,0 +1,360 @@
/*
* 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);
}
}