Make ContentCapture calls directly from the UI thread.
Most calls are bufffered anyways, so by not using a handler we save time spent
on getting a pooled lambda and posting to the the handler.
The only "expensive" operation is flushing, but it makes a oneway binder call
so it might be fine (and if necessary, we can optimize it later).
Bug: 123307965
Test: atest CtsContentCaptureServiceTestCases
Change-Id: I7182c8e193f58fa000396fdb3003e771214bf79b
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index e8704af..e6c12c7 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -469,6 +469,11 @@
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
+ /** @hide */
+ public final boolean postDelayed(Runnable r, int what, long delayMillis) {
+ return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
+ }
+
/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 8ae9e3c..bf46e7e 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -120,6 +120,7 @@
}
/** @hide */
+ @UiThread
public void onActivityStarted(@NonNull IBinder applicationToken,
@NonNull ComponentName activityComponent, int flags) {
synchronized (mLock) {
@@ -129,6 +130,7 @@
}
/** @hide */
+ @UiThread
public void onActivityStopped() {
getMainContentCaptureSession().destroy();
}
@@ -140,6 +142,7 @@
*
* @hide
*/
+ @UiThread
public void flush(@FlushReason int reason) {
getMainContentCaptureSession().flush(reason);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 0605fa3..eb945b5 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -26,10 +26,9 @@
import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UiThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ParceledListSlice;
@@ -58,10 +57,6 @@
* <p>This session is created when the activity starts and finished when it stops; clients can use
* it to create children activities.
*
- * <p><b>NOTE: all methods in this class should return right away, or do the real work in a handler
- * thread. Hence, the only field that must be thread-safe is {@code mEnabled}, which is called at
- * the beginning of every method.
- *
* @hide
*/
public final class MainContentCaptureSession extends ContentCaptureSession {
@@ -168,46 +163,28 @@
*
* @hide
*/
- void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
+ @UiThread
+ void start(@NonNull IBinder token, @NonNull ComponentName component,
int flags) {
if (!isContentCaptureEnabled()) return;
if (VERBOSE) {
- Log.v(TAG, "start(): token=" + applicationToken + ", comp="
- + ComponentName.flattenToShortString(activityComponent));
+ Log.v(TAG, "start(): token=" + token + ", comp="
+ + ComponentName.flattenToShortString(component));
}
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
- applicationToken, activityComponent, flags));
- }
-
- @Override
- void flush(@FlushReason int reason) {
- mHandler.sendMessage(
- obtainMessage(MainContentCaptureSession::handleForceFlush, this, reason));
- }
-
- @Override
- void onDestroy() {
- mHandler.removeMessages(MSG_FLUSH);
- mHandler.sendMessage(
- obtainMessage(MainContentCaptureSession::handleDestroySession, this));
- }
-
- private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
- int flags) {
- if (handleHasStarted()) {
+ if (hasStarted()) {
// TODO(b/122959591): make sure this is expected (and when), or use Log.w
if (DEBUG) {
Log.d(TAG, "ignoring handleStartSession(" + token + "/"
- + ComponentName.flattenToShortString(componentName) + " while on state "
+ + ComponentName.flattenToShortString(component) + " while on state "
+ getStateAsString(mState));
}
return;
}
mState = STATE_WAITING_FOR_SERVER;
mApplicationToken = token;
- mComponentName = componentName;
+ mComponentName = component;
if (VERBOSE) {
Log.v(TAG, "handleStartSession(): token=" + token + ", act="
@@ -217,28 +194,36 @@
try {
if (mSystemServerInterface == null) return;
- mSystemServerInterface.startSession(mApplicationToken, componentName, mId, flags,
+ mSystemServerInterface.startSession(mApplicationToken, component, mId, flags,
new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) {
- IBinder binder = null;
+ final IBinder binder;
if (resultData != null) {
binder = resultData.getBinder(EXTRA_BINDER);
if (binder == null) {
Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
- handleResetSession(STATE_DISABLED | STATE_INTERNAL_ERROR);
+ mHandler.post(() -> resetSession(
+ STATE_DISABLED | STATE_INTERNAL_ERROR));
return;
}
+ } else {
+ binder = null;
}
- handleSessionStarted(resultCode, binder);
+ mHandler.post(() -> onSessionStarted(resultCode, binder));
}
});
} catch (RemoteException e) {
- Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
- + e);
+ Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
}
}
+ @Override
+ void onDestroy() {
+ mHandler.removeMessages(MSG_FLUSH);
+ mHandler.post(() -> destroySession());
+ }
+
/**
* Callback from {@code system_server} after call to
* {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
@@ -247,7 +232,8 @@
* @param resultCode session state
* @param binder handle to {@code IContentCaptureDirectManager}
*/
- private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
+ @UiThread
+ private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
if (binder != null) {
mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
mDirectServiceVulture = () -> {
@@ -262,7 +248,7 @@
}
if ((resultCode & STATE_DISABLED) != 0) {
- handleResetSession(resultCode);
+ resetSession(resultCode);
} else {
mState = resultCode;
mDisabled.set(false);
@@ -274,14 +260,16 @@
}
}
- private void handleSendEvent(@NonNull ContentCaptureEvent event) {
- handleSendEvent(event, /* forceFlush= */ false);
+ @UiThread
+ private void sendEvent(@NonNull ContentCaptureEvent event) {
+ sendEvent(event, /* forceFlush= */ false);
}
- private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ @UiThread
+ private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
final int eventType = event.getType();
if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
- if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
+ if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
// TODO(b/120494182): comment when this could happen (dialogs?)
Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
+ ContentCaptureEvent.getTypeAsString(eventType)
@@ -342,7 +330,7 @@
final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
if (bufferEvent && !forceFlush) {
- handleScheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
+ scheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
return;
}
@@ -356,7 +344,7 @@
Log.d(TAG, "Closing session for " + getDebugState()
+ " after " + numberEvents + " delayed events");
}
- handleResetSession(STATE_DISABLED | STATE_NO_RESPONSE);
+ resetSession(STATE_DISABLED | STATE_NO_RESPONSE);
// TODO(b/111276913): blacklist activity / use special flag to indicate that
// when it's launched again
return;
@@ -373,19 +361,21 @@
flushReason = FLUSH_REASON_FULL;
}
- handleForceFlush(flushReason);
+ flush(flushReason);
}
- private boolean handleHasStarted() {
+ @UiThread
+ private boolean hasStarted() {
return mState != UNKNOWN_STATE;
}
- private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
+ @UiThread
+ private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
if (VERBOSE) {
Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+ ", checkExisting=" + checkExisting);
}
- if (!handleHasStarted()) {
+ if (!hasStarted()) {
if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
return;
}
@@ -406,20 +396,22 @@
Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
+ FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
}
- mHandler.sendMessageDelayed(
- obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this, reason)
- .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
+ // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
+ mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, FLUSHING_FREQUENCY_MS);
}
- private void handleFlushIfNeeded(@FlushReason int reason) {
+ @UiThread
+ private void flushIfNeeded(@FlushReason int reason) {
if (mEvents == null || mEvents.isEmpty()) {
if (VERBOSE) Log.v(TAG, "Nothing to flush");
return;
}
- handleForceFlush(reason);
+ flush(reason);
}
- private void handleForceFlush(@FlushReason int reason) {
+ @Override
+ @UiThread
+ void flush(@FlushReason int reason) {
if (mEvents == null) return;
if (mDisabled.get()) {
@@ -434,7 +426,7 @@
+ "client not ready: " + mEvents);
}
if (!mHandler.hasMessages(MSG_FLUSH)) {
- handleScheduleFlush(reason, /* checkExisting= */ false);
+ scheduleFlush(reason, /* checkExisting= */ false);
}
return;
}
@@ -451,7 +443,7 @@
mFlushHistory.log(logRecord);
mHandler.removeMessages(MSG_FLUSH);
- final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
+ final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
mDirectServiceInterface.sendEvents(events);
} catch (RemoteException e) {
Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
@@ -463,7 +455,8 @@
* Resets the buffer and return a {@link ParceledListSlice} with the previous events.
*/
@NonNull
- private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
+ @UiThread
+ private ParceledListSlice<ContentCaptureEvent> clearEvents() {
// NOTE: we must save a reference to the current mEvents and then set it to to null,
// otherwise clearing it would clear it in the receiving side if the service is also local.
final List<ContentCaptureEvent> events = mEvents == null
@@ -473,7 +466,8 @@
return new ParceledListSlice<>(events);
}
- private void handleDestroySession() {
+ @UiThread
+ private void destroySession() {
if (DEBUG) {
Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -492,7 +486,8 @@
// TODO(b/122454205): once we support multiple sessions, we might need to move some of these
// clearings out.
- private void handleResetSession(int newState) {
+ @UiThread
+ private void resetSession(int newState) {
if (VERBOSE) {
Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -545,28 +540,25 @@
// change should also get get rid of the "internalNotifyXXXX" methods above
void notifyChildSessionStarted(@NonNull String parentSessionId,
@NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
- .setParentSessionId(parentSessionId)
- .setClientContext(clientContext),
- FORCE_FLUSH));
+ sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ .setParentSessionId(parentSessionId).setClientContext(clientContext),
+ FORCE_FLUSH);
}
void notifyChildSessionFinished(@NonNull String parentSessionId,
@NonNull String childSessionId) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
- .setParentSessionId(parentSessionId), FORCE_FLUSH));
+ sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId), FORCE_FLUSH);
}
void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED).setViewNode(node.mNode)));
+ sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+ .setViewNode(node.mNode));
}
void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
+ sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
}
/** @hide */
@@ -578,27 +570,21 @@
} else {
event.setAutofillIds(ids);
}
-
- mHandler.sendMessage(
- obtainMessage(MainContentCaptureSession::handleSendEvent, this, event));
+ sendEvent(event);
}
void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
@Nullable CharSequence text) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
- .setText(text)));
+ sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
+ .setText(text));
}
void notifyInitialViewHierarchyEvent(@NonNull String sessionId, boolean started) {
if (started) {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARING)));
+ sendEvent(new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARING));
} else {
- mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARED),
- FORCE_FLUSH));
-
+ sendEvent(new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARED),
+ FORCE_FLUSH);
}
}