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

Commit b9687849 authored by Felipe Leme's avatar Felipe Leme
Browse files

Optimizes the Content Capture workflow by calling the service directly.

Initially, the ContentCaptureManager (in the app) was calling the
IContentCaptureManager (on system server) for everything, even to pass the
list of captured events, which caused 2 IPCs for each batch of events (i.e.,
from app to system_server, then from system_service to service).

This CL optimizes the workflow by getting rid of the "middle man" and sending
the events from the app to the service directly, which the system_server only
calling the service to notify when the view starts and finishes (and passing
the UID in the former so the servier can validate the sendEvents() calls).

Bug: 119220549
Test: atest CtsContentCaptureServiceTestCases

Change-Id: I6c08dccf755605320ac37cbc9424132e5455a594
parent c0cd1d7f
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -357,6 +357,7 @@ java_defaults {
        "core/java/android/view/autofill/IAutoFillManagerClient.aidl",
        "core/java/android/view/autofill/IAutoFillManagerClient.aidl",
        "core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl",
        "core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl",
        "core/java/android/view/autofill/IAutofillWindowPresenter.aidl",
        "core/java/android/view/autofill/IAutofillWindowPresenter.aidl",
        "core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl",
        "core/java/android/view/contentcapture/IContentCaptureManager.aidl",
        "core/java/android/view/contentcapture/IContentCaptureManager.aidl",
        "core/java/android/view/IApplicationToken.aidl",
        "core/java/android/view/IApplicationToken.aidl",
        "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl",
        "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl",
+1 −1
Original line number Original line Diff line number Diff line
@@ -5017,7 +5017,7 @@ package android.service.contentcapture {
    method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
    method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
    method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
    method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
    method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData);
    method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData);
    method public abstract void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
    method public void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
    method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
    method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
    method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
    method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
    method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
    method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
+6 −6
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@ package android.service.contentcapture;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.SystemApi;
import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureEvent;
@@ -31,10 +32,10 @@ import java.util.List;
@SystemApi
@SystemApi
public final class ContentCaptureEventsRequest implements Parcelable {
public final class ContentCaptureEventsRequest implements Parcelable {


    private final List<ContentCaptureEvent> mEvents;
    private final ParceledListSlice<ContentCaptureEvent> mEvents;


    /** @hide */
    /** @hide */
    public ContentCaptureEventsRequest(@NonNull List<ContentCaptureEvent> events) {
    public ContentCaptureEventsRequest(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
        mEvents = events;
        mEvents = events;
    }
    }


@@ -43,7 +44,7 @@ public final class ContentCaptureEventsRequest implements Parcelable {
     */
     */
    @NonNull
    @NonNull
    public List<ContentCaptureEvent> getEvents() {
    public List<ContentCaptureEvent> getEvents() {
        return mEvents;
        return mEvents.getList();
    }
    }


    @Override
    @Override
@@ -53,7 +54,7 @@ public final class ContentCaptureEventsRequest implements Parcelable {


    @Override
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeTypedList(mEvents, flags);
        parcel.writeParcelable(mEvents, flags);
    }
    }


    public static final Parcelable.Creator<ContentCaptureEventsRequest> CREATOR =
    public static final Parcelable.Creator<ContentCaptureEventsRequest> CREATOR =
@@ -61,8 +62,7 @@ public final class ContentCaptureEventsRequest implements Parcelable {


        @Override
        @Override
        public ContentCaptureEventsRequest createFromParcel(Parcel parcel) {
        public ContentCaptureEventsRequest createFromParcel(Parcel parcel) {
            return new ContentCaptureEventsRequest(parcel
            return new ContentCaptureEventsRequest(parcel.readParcelable(null));
                    .createTypedArrayList(ContentCaptureEvent.CREATOR));
        }
        }


        @Override
        @Override
+123 −31
Original line number Original line Diff line number Diff line
@@ -24,15 +24,27 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.app.Service;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Log;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.IContentCaptureDirectManager;


import com.android.internal.os.IResultReceiver;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.List;
import java.util.Set;
import java.util.Set;


@@ -63,39 +75,54 @@ public abstract class ContentCaptureService extends Service {


    private Handler mHandler;
    private Handler mHandler;


    private final IContentCaptureService mInterface = new IContentCaptureService.Stub() {
    /**
     * Binder that receives calls from the system server.
     */
    private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {


        @Override
        @Override
        public void onSessionLifecycle(ContentCaptureContext context, String sessionId)
        public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid,
                throws RemoteException {
                IResultReceiver clientReceiver) {
            if (context != null) {
            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession,
                mHandler.sendMessage(
                    ContentCaptureService.this, context, sessionId, uid, clientReceiver));
                        obtainMessage(ContentCaptureService::handleOnCreateSession,
                                ContentCaptureService.this, context, sessionId));
            } else {
                mHandler.sendMessage(
                        obtainMessage(ContentCaptureService::handleOnDestroySession,
                                ContentCaptureService.this, sessionId));
            }
        }
        }


        @Override
        @Override
        public void onContentCaptureEventsRequest(String sessionId,
        public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) {
                ContentCaptureEventsRequest request) {
            mHandler.sendMessage(
            mHandler.sendMessage(
                    obtainMessage(ContentCaptureService::handleOnContentCaptureEventsRequest,
                    obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
                            ContentCaptureService.this, sessionId, request));
                            ContentCaptureService.this, sessionId, snapshotData));
        }


        @Override
        public void onSessionFinished(String sessionId) {
            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
                    ContentCaptureService.this, sessionId));
        }
        }
    };

    /**
     * Binder that receives calls from the app.
     */
    private final IContentCaptureDirectManager mClientInterface =
            new IContentCaptureDirectManager.Stub() {


        @Override
        @Override
        public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) {
        public void sendEvents(String sessionId,
            mHandler.sendMessage(
                @SuppressWarnings("rawtypes") ParceledListSlice events) {
                    obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents,
                            ContentCaptureService.this, sessionId, snapshotData));
                            ContentCaptureService.this, sessionId, Binder.getCallingUid(), events));
        }
        }
    };
    };


    /**
     * List of sessions per UID.
     *
     * <p>This map is populated when an session is started, which is called by the system server
     * and can be trusted. Then subsequent calls made by the app are verified against this map.
     */
    private final ArrayMap<String, Integer> mSessionsByUid = new ArrayMap<>();

    @CallSuper
    @CallSuper
    @Override
    @Override
    public void onCreate() {
    public void onCreate() {
@@ -107,7 +134,7 @@ public abstract class ContentCaptureService extends Service {
    @Override
    @Override
    public final IBinder onBind(Intent intent) {
    public final IBinder onBind(Intent intent) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
            return mInterface.asBinder();
            return mServerInterface.asBinder();
        }
        }
        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
        return null;
        return null;
@@ -196,8 +223,10 @@ public abstract class ContentCaptureService extends Service {
     * @param sessionId the session's Id
     * @param sessionId the session's Id
     * @param request the events
     * @param request the events
     */
     */
    public abstract void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
    public void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
            @NonNull ContentCaptureEventsRequest request);
            @NonNull ContentCaptureEventsRequest request) {
        if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
    }


    /**
    /**
     * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
     * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
@@ -212,10 +241,22 @@ public abstract class ContentCaptureService extends Service {
     * Destroys the content capture session.
     * Destroys the content capture session.
     *
     *
     * @param sessionId the id of the session to destroy
     * @param sessionId the id of the session to destroy
     */
     * */
    public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
    public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
        if (VERBOSE) {
        if (VERBOSE) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
            Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
    }

    @Override
    @CallSuper
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        final int size = mSessionsByUid.size();
        pw.print("Number sessions: "); pw.println(size);
        if (size > 0) {
            final String prefix = "  ";
            for (int i = 0; i < size; i++) {
                pw.print(prefix); pw.print(mSessionsByUid.keyAt(i));
                pw.print(": uid="); pw.println(mSessionsByUid.valueAt(i));
            }
        }
        }
    }
    }


@@ -223,13 +264,19 @@ public abstract class ContentCaptureService extends Service {
    // so we don't need to create a temporary InteractionSessionId for each event.
    // so we don't need to create a temporary InteractionSessionId for each event.


    private void handleOnCreateSession(@NonNull ContentCaptureContext context,
    private void handleOnCreateSession(@NonNull ContentCaptureContext context,
            @NonNull String sessionId) {
            @NonNull String sessionId, int uid, IResultReceiver clientReceiver) {
        mSessionsByUid.put(sessionId, uid);
        onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
        onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
        setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
                mClientInterface.asBinder());
    }
    }


    private void handleOnContentCaptureEventsRequest(@NonNull String sessionId,
    private void handleSendEvents(@NonNull String sessionId, int uid,
            @NonNull ContentCaptureEventsRequest request) {
            @NonNull ParceledListSlice<ContentCaptureEvent> events) {
        onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), request);
        if (handleIsRightCallerFor(sessionId, uid)) {
            onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId),
                    new ContentCaptureEventsRequest(events));
        }
    }
    }


    private void handleOnActivitySnapshot(@NonNull String sessionId,
    private void handleOnActivitySnapshot(@NonNull String sessionId,
@@ -237,7 +284,52 @@ public abstract class ContentCaptureService extends Service {
        onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
        onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
    }
    }


    private void handleOnDestroySession(@NonNull String sessionId) {
    private void handleFinishSession(@NonNull String sessionId) {
        mSessionsByUid.remove(sessionId);
        onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
        onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
    }
    }

    /**
     * Checks if the given {@code uid} owns the session.
     */
    private boolean handleIsRightCallerFor(@NonNull String sessionId, int uid) {
        final Integer rightUid = mSessionsByUid.get(sessionId);
        if (rightUid == null) {
            if (VERBOSE) Log.v(TAG, "No session for " + sessionId);
            // Just ignore, as the session could have finished
            return false;
        }
        if (rightUid != uid) {
            Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
                    + rightUid);
            //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId
            return false;
        }
        return true;

    }

    /**
     * Sends the state of the {@link ContentCaptureManager} in the cleint app.
     *
     * @param clientReceiver receiver in the client app.
     * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
     * service.
     * @hide
     */
    public static void setClientState(@NonNull IResultReceiver clientReceiver,
            int sessionStatus, @Nullable IBinder binder) {
        try {
            final Bundle extras;
            if (binder != null) {
                extras = new Bundle();
                extras.putBinder(ContentCaptureSession.EXTRA_BINDER, binder);
            } else {
                extras = null;
            }
            clientReceiver.send(sessionStatus, extras);
        } catch (RemoteException e) {
            Slog.w(TAG, "Error async reporting result to client: " + e);
        }
    }
}
}
+5 −7
Original line number Original line Diff line number Diff line
@@ -16,10 +16,11 @@


package android.service.contentcapture;
package android.service.contentcapture;


import android.service.contentcapture.ContentCaptureEventsRequest;
import android.service.contentcapture.SnapshotData;
import android.service.contentcapture.SnapshotData;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureContext;


import com.android.internal.os.IResultReceiver;

import java.util.List;
import java.util.List;


/**
/**
@@ -28,11 +29,8 @@ import java.util.List;
 * @hide
 * @hide
 */
 */
oneway interface IContentCaptureService {
oneway interface IContentCaptureService {

    void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid,
    // Called when session is created (context not null) or destroyed (context null)
                          in IResultReceiver clientReceiver);
    void onSessionLifecycle(in ContentCaptureContext context, String sessionId);
    void onSessionFinished(String sessionId);

    void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request);

    void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
    void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
}
}
Loading