Loading apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -18,21 +18,31 @@ package android.view.contentcapture; import static android.view.contentcapture.CustomTestActivity.VIEW_TYPE_CUSTOM_VIEW; import static android.view.contentcapture.CustomTestActivity.VIEW_TYPE_TEXT_VIEW; import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED; import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.os.RemoteCallback; import android.perftests.utils.BenchmarkState; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.service.contentcapture.ContentCaptureService; import android.view.View; import android.view.contentcapture.flags.Flags; import androidx.test.filters.LargeTest; import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; import com.android.perftests.contentcapture.R; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @LargeTest public class LoginTest extends AbstractContentCapturePerfTestCase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void testLaunchActivity() throws Throwable { Loading Loading @@ -300,4 +310,45 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { state.resumeTiming(); } } @Test @RequiresFlagsEnabled(Flags.FLAG_REDUCE_BINDER_TRANSACTION_ENABLED) public void testBatchFlushMetrics_flagEnabled() throws Throwable { // Arrange MyContentCaptureService service = enableService(); CustomTestActivity activity = launchActivity( R.layout.test_export_virtual_assist_node_activity, 3, VIEW_TYPE_TEXT_VIEW); View groupRootView = activity.findViewById(R.id.group_root_view); int sessionId = groupRootView.getContentCaptureSession().getId(); int expectedFlushCount = 2; int expectedViewAppearedCount = 4; // 3 TextViews + 1 container int eventTimeoutMs = 10000; BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); // Act while (state.keepRunning()) { state.pauseTiming(); service.clearEvents(); ContentCaptureService.PendingMetrics pendingMetrics = service.mPendingMetrics.get(sessionId); if (pendingMetrics != null) { pendingMetrics.getMetrics().reset(); } sInstrumentation.runOnMainSync(() -> groupRootView.setVisibility(View.GONE)); sInstrumentation.waitForIdleSync(); state.resumeTiming(); sInstrumentation.runOnMainSync(() -> groupRootView.setVisibility(View.VISIBLE)); sInstrumentation.waitForIdleSync(); state.pauseTiming(); service.waitForFlushEvents(expectedFlushCount, eventTimeoutMs); // Assert assertThat(service.getAppearedCount()).isEqualTo(expectedViewAppearedCount); assertThat(service.mPendingMetrics.get(sessionId).getMetrics().viewAppearedCount) .isEqualTo(expectedViewAppearedCount); state.resumeTiming(); } } } apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java +37 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ public class MyContentCaptureService extends ContentCaptureService { private final Condition eventsChanged = lock.newCondition(); private final List<ContentCaptureEvent> mCapturedEvents = new ArrayList<>(); private int appearedCount = 0; private int flushCount = 0; @NonNull public static ServiceWatcher setServiceWatcher() { if (sServiceWatcher != null) { Loading Loading @@ -137,6 +138,8 @@ public class MyContentCaptureService extends ContentCaptureService { mCapturedEvents.add(event); if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) { appearedCount++; } else if (event.getType() == ContentCaptureEvent.TYPE_SESSION_FLUSH) { flushCount++; } eventsChanged.signalAll(); } finally { Loading @@ -153,6 +156,7 @@ public class MyContentCaptureService extends ContentCaptureService { try { mCapturedEvents.clear(); appearedCount = 0; flushCount = 0; } finally { lock.unlock(); } Loading @@ -177,19 +181,50 @@ public class MyContentCaptureService extends ContentCaptureService { } } public int getFlushCount() { lock.lock(); try { return flushCount; } finally { lock.unlock(); } } public boolean waitForAppearedEvents( int expectedCount, long timeoutMillis) throws InterruptedException { return waitForEvents(expectedCount, timeoutMillis, ContentCaptureEvent.TYPE_VIEW_APPEARED); } public boolean waitForFlushEvents( int expectedCount, long timeoutMillis) throws InterruptedException { return waitForEvents(expectedCount, timeoutMillis, ContentCaptureEvent.TYPE_SESSION_FLUSH); } private boolean waitForEvents(int expectedCount, long timeoutMillis, int type) throws InterruptedException { long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMillis); lock.lock(); try { while (appearedCount < expectedCount) { while (true) { final int actualCount; if (type == ContentCaptureEvent.TYPE_VIEW_APPEARED) { actualCount = appearedCount; } else if (type == ContentCaptureEvent.TYPE_SESSION_FLUSH) { actualCount = flushCount; } else { return false; } if (actualCount >= expectedCount) { return true; } long remainingNanos = deadline - System.nanoTime(); if (remainingNanos <= 0) { return false; } eventsChanged.await(remainingNanos, TimeUnit.NANOSECONDS); } return true; } finally { lock.unlock(); } Loading core/java/android/service/contentcapture/ContentCaptureService.java +199 −0 Original line number Diff line number Diff line Loading @@ -22,9 +22,12 @@ import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; import static android.view.contentcapture.flags.Flags.reduceBinderTransactionEnabled; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.CallSuper; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; Loading @@ -44,6 +47,7 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureContext; Loading @@ -55,6 +59,7 @@ import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureDirectManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.util.FrameworkStatsLog; Loading Loading @@ -143,6 +148,23 @@ public abstract class ContentCaptureService extends Service { */ public static final String ASSIST_CONTENT_ACTIVITY_START_KEY = "activity_start_assist_content"; /** * The threshold for the number of metrics to be flushed before flushing the pending metrics. * * <p>For example, if the threshold is 10,000, then the metrics will be flushed when there are * 10,000 or more pending view appeared or disappeared metrics. */ private static final int METRICS_FLUSH_THRESHOLD = 10_000; /** * Holds metrics that are waiting to be flushed. * * <p>Key is the session id. * * @hide */ @VisibleForTesting public final SparseArray<PendingMetrics> mPendingMetrics = new SparseArray<>(); private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); Loading Loading @@ -508,6 +530,12 @@ public abstract class ContentCaptureService extends Service { private void handleOnDisconnected() { onDisconnected(); if (reduceBinderTransactionEnabled()) { if (mPendingMetrics.size() != 0) { Log.i(TAG, "Flushing " + mPendingMetrics.size() + " pending metrics on disconnect"); flushAllPendingMetrics(); } } mContentCaptureServiceCallback = null; mContentProtectionAllowlistCallback = null; } Loading Loading @@ -536,9 +564,21 @@ public abstract class ContentCaptureService extends Service { setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder()); } @MainThread private void handleSendEvents(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { if (reduceBinderTransactionEnabled()) { handleSendEventsWithBatching(uid, parceledEvents, reason, options); } else { handleSendEventsNoBatching(uid, parceledEvents, reason, options); } } @MainThread private void handleSendEventsNoBatching(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { final List<ContentCaptureEvent> events = parceledEvents.getList(); if (events.isEmpty()) { Log.w(TAG, "handleSendEvents() received empty list of events"); Loading Loading @@ -601,6 +641,92 @@ public abstract class ContentCaptureService extends Service { writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason); } @MainThread private void handleSendEventsWithBatching(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { final List<ContentCaptureEvent> events = parceledEvents.getList(); if (events.isEmpty()) { Log.w(TAG, "handleSendEventsWithBatching() received empty list of events"); return; } int lastSessionId = NO_SESSION_ID; ContentCaptureSessionId sessionId = null; for (int i = 0; i < events.size(); i++) { final ContentCaptureEvent event = events.get(i); if (!handleIsRightCallerFor(event, uid)) continue; final int sessionIdInt = event.getSessionId(); if (sessionIdInt != lastSessionId) { sessionId = new ContentCaptureSessionId(sessionIdInt); lastSessionId = sessionIdInt; } final int eventType = event.getType(); PendingMetrics pendingMetrics = mPendingMetrics.get(sessionIdInt); if (pendingMetrics == null) { pendingMetrics = new PendingMetrics(); mPendingMetrics.put(sessionIdInt, pendingMetrics); } final ContentCaptureContext clientContext = event.getContentCaptureContext(); pendingMetrics.update( clientContext != null ? clientContext.getActivityComponent() : null, options, reason); boolean shouldFlushMetrics = false; switch (eventType) { case ContentCaptureEvent.TYPE_SESSION_STARTED: clientContext.setParentSessionId(event.getParentSessionId()); mSessionUids.put(sessionIdInt, uid); onCreateContentCaptureSession(clientContext, sessionId); pendingMetrics.metrics.sessionStarted++; break; case ContentCaptureEvent.TYPE_SESSION_FINISHED: mSessionUids.delete(sessionIdInt); onDestroyContentCaptureSession(sessionId); pendingMetrics.metrics.sessionFinished++; // Flush immediately when session is finished, regardless of threshold. shouldFlushMetrics = true; break; case ContentCaptureEvent.TYPE_SESSION_PAUSED: onContentCaptureEvent(sessionId, event); // Flush immediately when session is paused, regardless of threshold. shouldFlushMetrics = true; break; case ContentCaptureEvent.TYPE_VIEW_APPEARED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewAppearedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewDisappearedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewTextChangedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; default: onContentCaptureEvent(sessionId, event); } if (shouldFlushMetrics) { flushMetricsForSession(sessionIdInt); } } } private static boolean isOverThreshold(@NonNull FlushMetrics metrics) { return metrics.viewAppearedCount >= METRICS_FLUSH_THRESHOLD || metrics.viewDisappearedCount >= METRICS_FLUSH_THRESHOLD || metrics.viewTextChangedCount >= METRICS_FLUSH_THRESHOLD; } private void handleOnLoginDetected( int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) { if (uid != Process.SYSTEM_UID) { Loading Loading @@ -638,6 +764,9 @@ public abstract class ContentCaptureService extends Service { private void handleFinishSession(int sessionId) { mSessionUids.delete(sessionId); if (reduceBinderTransactionEnabled()) { flushMetricsForSession(sessionId); } onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } Loading Loading @@ -744,6 +873,47 @@ public abstract class ContentCaptureService extends Service { } } @MainThread private void flushAllPendingMetrics() { if (mPendingMetrics.size() == 0) { if (sDebug) { Log.d(TAG, "flushAllPendingMetrics() - nothing to flush"); } return; } if (sDebug) { Log.d(TAG, "Flushing metrics for " + mPendingMetrics.size() + " sessions"); } for (int i = 0; i < mPendingMetrics.size(); i++) { final int sessionId = mPendingMetrics.keyAt(i); final PendingMetrics pendingMetrics = mPendingMetrics.valueAt(i); writeFlushMetrics(sessionId, pendingMetrics.activityComponent, pendingMetrics.metrics, pendingMetrics.options, pendingMetrics.flushReason); } mPendingMetrics.clear(); } @MainThread private void flushMetricsForSession(int sessionId) { int index = mPendingMetrics.indexOfKey(sessionId); if (index < 0) { return; } final PendingMetrics pendingMetrics = mPendingMetrics.get(sessionId); mPendingMetrics.removeAt(index); if (pendingMetrics == null) { return; } if (sDebug) { Log.d(TAG, "Flushing metrics for session " + sessionId); } writeFlushMetrics(sessionId, pendingMetrics.activityComponent, pendingMetrics.metrics, pendingMetrics.options, pendingMetrics.flushReason); } /** * Logs the metrics for content capture events flushing. */ Loading @@ -763,6 +933,35 @@ public abstract class ContentCaptureService extends Service { } } /** * Container for metrics that are batched before being flushed. * * @hide */ @VisibleForTesting public static class PendingMetrics { private final FlushMetrics metrics = new FlushMetrics(); @Nullable private ComponentName activityComponent; @Nullable private ContentCaptureOptions options; private int flushReason; private void update( @Nullable ComponentName activityComponent, @Nullable ContentCaptureOptions options, int flushReason) { if (this.activityComponent == null && activityComponent != null) { this.activityComponent = activityComponent; } this.options = options; this.flushReason = flushReason; } @VisibleForTesting public FlushMetrics getMetrics() { return metrics; } } private static class DataShareReadAdapterDelegate extends IDataShareReadAdapter.Stub { private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference; Loading core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +6 −0 Original line number Diff line number Diff line Loading @@ -52,3 +52,9 @@ flag { bug: "430421182" } flag { name: "reduce_binder_transaction_enabled" namespace: "ailabs" description: "Feature flag to reduce binder transaction for content capture, by batching metrics logging and reducing the number of flush events" bug: "433614684" } Loading
apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -18,21 +18,31 @@ package android.view.contentcapture; import static android.view.contentcapture.CustomTestActivity.VIEW_TYPE_CUSTOM_VIEW; import static android.view.contentcapture.CustomTestActivity.VIEW_TYPE_TEXT_VIEW; import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED; import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.os.RemoteCallback; import android.perftests.utils.BenchmarkState; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.service.contentcapture.ContentCaptureService; import android.view.View; import android.view.contentcapture.flags.Flags; import androidx.test.filters.LargeTest; import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; import com.android.perftests.contentcapture.R; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @LargeTest public class LoginTest extends AbstractContentCapturePerfTestCase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void testLaunchActivity() throws Throwable { Loading Loading @@ -300,4 +310,45 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { state.resumeTiming(); } } @Test @RequiresFlagsEnabled(Flags.FLAG_REDUCE_BINDER_TRANSACTION_ENABLED) public void testBatchFlushMetrics_flagEnabled() throws Throwable { // Arrange MyContentCaptureService service = enableService(); CustomTestActivity activity = launchActivity( R.layout.test_export_virtual_assist_node_activity, 3, VIEW_TYPE_TEXT_VIEW); View groupRootView = activity.findViewById(R.id.group_root_view); int sessionId = groupRootView.getContentCaptureSession().getId(); int expectedFlushCount = 2; int expectedViewAppearedCount = 4; // 3 TextViews + 1 container int eventTimeoutMs = 10000; BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); // Act while (state.keepRunning()) { state.pauseTiming(); service.clearEvents(); ContentCaptureService.PendingMetrics pendingMetrics = service.mPendingMetrics.get(sessionId); if (pendingMetrics != null) { pendingMetrics.getMetrics().reset(); } sInstrumentation.runOnMainSync(() -> groupRootView.setVisibility(View.GONE)); sInstrumentation.waitForIdleSync(); state.resumeTiming(); sInstrumentation.runOnMainSync(() -> groupRootView.setVisibility(View.VISIBLE)); sInstrumentation.waitForIdleSync(); state.pauseTiming(); service.waitForFlushEvents(expectedFlushCount, eventTimeoutMs); // Assert assertThat(service.getAppearedCount()).isEqualTo(expectedViewAppearedCount); assertThat(service.mPendingMetrics.get(sessionId).getMetrics().viewAppearedCount) .isEqualTo(expectedViewAppearedCount); state.resumeTiming(); } } }
apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java +37 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ public class MyContentCaptureService extends ContentCaptureService { private final Condition eventsChanged = lock.newCondition(); private final List<ContentCaptureEvent> mCapturedEvents = new ArrayList<>(); private int appearedCount = 0; private int flushCount = 0; @NonNull public static ServiceWatcher setServiceWatcher() { if (sServiceWatcher != null) { Loading Loading @@ -137,6 +138,8 @@ public class MyContentCaptureService extends ContentCaptureService { mCapturedEvents.add(event); if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) { appearedCount++; } else if (event.getType() == ContentCaptureEvent.TYPE_SESSION_FLUSH) { flushCount++; } eventsChanged.signalAll(); } finally { Loading @@ -153,6 +156,7 @@ public class MyContentCaptureService extends ContentCaptureService { try { mCapturedEvents.clear(); appearedCount = 0; flushCount = 0; } finally { lock.unlock(); } Loading @@ -177,19 +181,50 @@ public class MyContentCaptureService extends ContentCaptureService { } } public int getFlushCount() { lock.lock(); try { return flushCount; } finally { lock.unlock(); } } public boolean waitForAppearedEvents( int expectedCount, long timeoutMillis) throws InterruptedException { return waitForEvents(expectedCount, timeoutMillis, ContentCaptureEvent.TYPE_VIEW_APPEARED); } public boolean waitForFlushEvents( int expectedCount, long timeoutMillis) throws InterruptedException { return waitForEvents(expectedCount, timeoutMillis, ContentCaptureEvent.TYPE_SESSION_FLUSH); } private boolean waitForEvents(int expectedCount, long timeoutMillis, int type) throws InterruptedException { long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMillis); lock.lock(); try { while (appearedCount < expectedCount) { while (true) { final int actualCount; if (type == ContentCaptureEvent.TYPE_VIEW_APPEARED) { actualCount = appearedCount; } else if (type == ContentCaptureEvent.TYPE_SESSION_FLUSH) { actualCount = flushCount; } else { return false; } if (actualCount >= expectedCount) { return true; } long remainingNanos = deadline - System.nanoTime(); if (remainingNanos <= 0) { return false; } eventsChanged.await(remainingNanos, TimeUnit.NANOSECONDS); } return true; } finally { lock.unlock(); } Loading
core/java/android/service/contentcapture/ContentCaptureService.java +199 −0 Original line number Diff line number Diff line Loading @@ -22,9 +22,12 @@ import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; import static android.view.contentcapture.flags.Flags.reduceBinderTransactionEnabled; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.CallSuper; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; Loading @@ -44,6 +47,7 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureContext; Loading @@ -55,6 +59,7 @@ import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureDirectManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.util.FrameworkStatsLog; Loading Loading @@ -143,6 +148,23 @@ public abstract class ContentCaptureService extends Service { */ public static final String ASSIST_CONTENT_ACTIVITY_START_KEY = "activity_start_assist_content"; /** * The threshold for the number of metrics to be flushed before flushing the pending metrics. * * <p>For example, if the threshold is 10,000, then the metrics will be flushed when there are * 10,000 or more pending view appeared or disappeared metrics. */ private static final int METRICS_FLUSH_THRESHOLD = 10_000; /** * Holds metrics that are waiting to be flushed. * * <p>Key is the session id. * * @hide */ @VisibleForTesting public final SparseArray<PendingMetrics> mPendingMetrics = new SparseArray<>(); private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); Loading Loading @@ -508,6 +530,12 @@ public abstract class ContentCaptureService extends Service { private void handleOnDisconnected() { onDisconnected(); if (reduceBinderTransactionEnabled()) { if (mPendingMetrics.size() != 0) { Log.i(TAG, "Flushing " + mPendingMetrics.size() + " pending metrics on disconnect"); flushAllPendingMetrics(); } } mContentCaptureServiceCallback = null; mContentProtectionAllowlistCallback = null; } Loading Loading @@ -536,9 +564,21 @@ public abstract class ContentCaptureService extends Service { setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder()); } @MainThread private void handleSendEvents(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { if (reduceBinderTransactionEnabled()) { handleSendEventsWithBatching(uid, parceledEvents, reason, options); } else { handleSendEventsNoBatching(uid, parceledEvents, reason, options); } } @MainThread private void handleSendEventsNoBatching(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { final List<ContentCaptureEvent> events = parceledEvents.getList(); if (events.isEmpty()) { Log.w(TAG, "handleSendEvents() received empty list of events"); Loading Loading @@ -601,6 +641,92 @@ public abstract class ContentCaptureService extends Service { writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason); } @MainThread private void handleSendEventsWithBatching(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options) { final List<ContentCaptureEvent> events = parceledEvents.getList(); if (events.isEmpty()) { Log.w(TAG, "handleSendEventsWithBatching() received empty list of events"); return; } int lastSessionId = NO_SESSION_ID; ContentCaptureSessionId sessionId = null; for (int i = 0; i < events.size(); i++) { final ContentCaptureEvent event = events.get(i); if (!handleIsRightCallerFor(event, uid)) continue; final int sessionIdInt = event.getSessionId(); if (sessionIdInt != lastSessionId) { sessionId = new ContentCaptureSessionId(sessionIdInt); lastSessionId = sessionIdInt; } final int eventType = event.getType(); PendingMetrics pendingMetrics = mPendingMetrics.get(sessionIdInt); if (pendingMetrics == null) { pendingMetrics = new PendingMetrics(); mPendingMetrics.put(sessionIdInt, pendingMetrics); } final ContentCaptureContext clientContext = event.getContentCaptureContext(); pendingMetrics.update( clientContext != null ? clientContext.getActivityComponent() : null, options, reason); boolean shouldFlushMetrics = false; switch (eventType) { case ContentCaptureEvent.TYPE_SESSION_STARTED: clientContext.setParentSessionId(event.getParentSessionId()); mSessionUids.put(sessionIdInt, uid); onCreateContentCaptureSession(clientContext, sessionId); pendingMetrics.metrics.sessionStarted++; break; case ContentCaptureEvent.TYPE_SESSION_FINISHED: mSessionUids.delete(sessionIdInt); onDestroyContentCaptureSession(sessionId); pendingMetrics.metrics.sessionFinished++; // Flush immediately when session is finished, regardless of threshold. shouldFlushMetrics = true; break; case ContentCaptureEvent.TYPE_SESSION_PAUSED: onContentCaptureEvent(sessionId, event); // Flush immediately when session is paused, regardless of threshold. shouldFlushMetrics = true; break; case ContentCaptureEvent.TYPE_VIEW_APPEARED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewAppearedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewDisappearedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED: onContentCaptureEvent(sessionId, event); pendingMetrics.metrics.viewTextChangedCount++; shouldFlushMetrics = isOverThreshold(pendingMetrics.metrics); break; default: onContentCaptureEvent(sessionId, event); } if (shouldFlushMetrics) { flushMetricsForSession(sessionIdInt); } } } private static boolean isOverThreshold(@NonNull FlushMetrics metrics) { return metrics.viewAppearedCount >= METRICS_FLUSH_THRESHOLD || metrics.viewDisappearedCount >= METRICS_FLUSH_THRESHOLD || metrics.viewTextChangedCount >= METRICS_FLUSH_THRESHOLD; } private void handleOnLoginDetected( int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) { if (uid != Process.SYSTEM_UID) { Loading Loading @@ -638,6 +764,9 @@ public abstract class ContentCaptureService extends Service { private void handleFinishSession(int sessionId) { mSessionUids.delete(sessionId); if (reduceBinderTransactionEnabled()) { flushMetricsForSession(sessionId); } onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } Loading Loading @@ -744,6 +873,47 @@ public abstract class ContentCaptureService extends Service { } } @MainThread private void flushAllPendingMetrics() { if (mPendingMetrics.size() == 0) { if (sDebug) { Log.d(TAG, "flushAllPendingMetrics() - nothing to flush"); } return; } if (sDebug) { Log.d(TAG, "Flushing metrics for " + mPendingMetrics.size() + " sessions"); } for (int i = 0; i < mPendingMetrics.size(); i++) { final int sessionId = mPendingMetrics.keyAt(i); final PendingMetrics pendingMetrics = mPendingMetrics.valueAt(i); writeFlushMetrics(sessionId, pendingMetrics.activityComponent, pendingMetrics.metrics, pendingMetrics.options, pendingMetrics.flushReason); } mPendingMetrics.clear(); } @MainThread private void flushMetricsForSession(int sessionId) { int index = mPendingMetrics.indexOfKey(sessionId); if (index < 0) { return; } final PendingMetrics pendingMetrics = mPendingMetrics.get(sessionId); mPendingMetrics.removeAt(index); if (pendingMetrics == null) { return; } if (sDebug) { Log.d(TAG, "Flushing metrics for session " + sessionId); } writeFlushMetrics(sessionId, pendingMetrics.activityComponent, pendingMetrics.metrics, pendingMetrics.options, pendingMetrics.flushReason); } /** * Logs the metrics for content capture events flushing. */ Loading @@ -763,6 +933,35 @@ public abstract class ContentCaptureService extends Service { } } /** * Container for metrics that are batched before being flushed. * * @hide */ @VisibleForTesting public static class PendingMetrics { private final FlushMetrics metrics = new FlushMetrics(); @Nullable private ComponentName activityComponent; @Nullable private ContentCaptureOptions options; private int flushReason; private void update( @Nullable ComponentName activityComponent, @Nullable ContentCaptureOptions options, int flushReason) { if (this.activityComponent == null && activityComponent != null) { this.activityComponent = activityComponent; } this.options = options; this.flushReason = flushReason; } @VisibleForTesting public FlushMetrics getMetrics() { return metrics; } } private static class DataShareReadAdapterDelegate extends IDataShareReadAdapter.Stub { private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference; Loading
core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +6 −0 Original line number Diff line number Diff line Loading @@ -52,3 +52,9 @@ flag { bug: "430421182" } flag { name: "reduce_binder_transaction_enabled" namespace: "ailabs" description: "Feature flag to reduce binder transaction for content capture, by batching metrics logging and reducing the number of flush events" bug: "433614684" }