diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index 33918400d5717..9471e69d21bdb 100644 --- a/core/java/com/android/internal/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; +import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; @@ -64,6 +65,8 @@ public abstract class AbstractRemoteService implements DeathRecipient { private static final int MSG_UNBIND = 1; + protected static final long PERMANENT_BOUND_TIMEOUT_MS = 0; + protected static final int LAST_PRIVATE_MSG = MSG_UNBIND; // TODO(b/117779333): convert all booleans into an integer / flags @@ -86,6 +89,9 @@ public abstract class AbstractRemoteServiceThis request must be responded by the service somehow (typically using a callback), * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the * service doesn't respond. + * + *

NOTE: this request is responsible for calling {@link #scheduleUnbind()}. */ protected void scheduleRequest(@NonNull PendingRequest pendingRequest) { cancelScheduledUnbind(); @@ -250,7 +272,7 @@ public abstract class AbstractRemoteService request) { - cancelScheduledUnbind(); + scheduleUnbind(); // TODO(b/117779333): fix generics below @SuppressWarnings({"unchecked", "rawtypes"}) final MyAsyncPendingRequest asyncRequest = new MyAsyncPendingRequest(this, request); @@ -263,12 +285,21 @@ public abstract class AbstractRemoteService { + AbstractPerUserSystemService + implements ContentCaptureServiceCallbacks { private static final String TAG = ContentCaptureManagerService.class.getSimpleName(); @@ -60,15 +63,52 @@ final class ContentCapturePerUserService private final ArrayMap mSessions = new ArrayMap<>(); + /** + * Reference to the remote service. + * + *

It's set in the constructor, but it's also updated when the service's updated in the + * master's cache (for example, because a temporary service was set). + */ + @GuardedBy("mLock") + private RemoteContentCaptureService mRemoteService; + // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's - protected ContentCapturePerUserService( - ContentCaptureManagerService master, Object lock, @UserIdInt int userId) { + ContentCapturePerUserService(@NonNull ContentCaptureManagerService master, + @NonNull Object lock, boolean disabled, @UserIdInt int userId) { super(master, lock, userId); + + updateRemoteServiceLocked(disabled); + } + + /** + * Updates the reference to the remote service. + */ + private void updateRemoteServiceLocked(boolean disabled) { + if (mRemoteService != null) { + if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service"); + mRemoteService.destroy(); + mRemoteService = null; + } + + // Updates the component name + final ComponentName serviceComponentName = updateServiceInfoLocked(); + + if (serviceComponentName == null) { + Slog.w(TAG, "updateRemoteService(): no service componennt name"); + return; + } + + if (!disabled) { + mRemoteService = new RemoteContentCaptureService( + mMaster.getContext(), + ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, mUserId, this, + mMaster.isBindInstantServiceAllowed(), mMaster.verbose); + } } @Override // from PerUserSystemService - protected ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent) + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) throws NameNotFoundException { int flags = PackageManager.GET_META_DATA; @@ -103,14 +143,24 @@ final class ContentCapturePerUserService @GuardedBy("mLock") protected boolean updateLocked(boolean disabled) { destroyLocked(); - return super.updateLocked(disabled); + final boolean disabledStateChanged = super.updateLocked(disabled); + updateRemoteServiceLocked(disabled); + return disabledStateChanged; } - // TODO(b/111276913): log metrics + @Override // from ContentCaptureServiceCallbacks + public void onServiceDied(@NonNull RemoteContentCaptureService service) { + if (mMaster.debug) Slog.d(TAG, "remote service died: " + service); + synchronized (mLock) { + removeSelfFromCacheLocked(); + } + } + + // TODO(b/119613670): log metrics @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @NonNull ComponentName componentName, int taskId, int displayId, - @NonNull String sessionId, int uid, int flags, boolean bindInstantServiceAllowed, + @NonNull String sessionId, int uid, int flags, @NonNull IResultReceiver clientReceiver) { if (!isEnabledLocked()) { setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null); @@ -136,10 +186,23 @@ final class ContentCapturePerUserService return; } - final ContentCaptureServerSession newSession = new ContentCaptureServerSession(getContext(), - mUserId, mLock, activityToken, this, serviceComponentName, componentName, taskId, - displayId, sessionId, uid, flags, bindInstantServiceAllowed, - mMaster.verbose); + if (mRemoteService == null) { + updateRemoteServiceLocked(/* disabled= */ false); // already checked for isEnabled + } + + if (mRemoteService == null) { + // TODO(b/119613670): log metrics + Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken + + ": ignoring because service is not set"); + // TODO(b/111276913): use a new disabled state? + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, + /* binder=*/ null); + return; + } + + final ContentCaptureServerSession newSession = new ContentCaptureServerSession( + activityToken, this, mRemoteService, componentName, taskId, + displayId, sessionId, uid, flags); if (mMaster.verbose) { Slog.v(TAG, "startSession(): new session for " + ComponentName.flattenToShortString(componentName) + " and id " + sessionId); @@ -148,7 +211,7 @@ final class ContentCapturePerUserService newSession.notifySessionStartedLocked(clientReceiver); } - // TODO(b/111276913): log metrics + // TODO(b/119613670): log metrics @GuardedBy("mLock") public void finishSessionLocked(@NonNull String sessionId) { if (!isEnabledLocked()) { @@ -239,12 +302,18 @@ final class ContentCapturePerUserService @Override protected void dumpLocked(String prefix, PrintWriter pw) { super.dumpLocked(prefix, pw); + + final String prefix2 = prefix + " "; + if (mRemoteService != null) { + pw.print(prefix); pw.println("remote service:"); + mRemoteService.dump(prefix2, pw); + } + if (mSessions.isEmpty()) { pw.print(prefix); pw.println("no sessions"); } else { final int size = mSessions.size(); pw.print(prefix); pw.print("number sessions: "); pw.println(size); - final String prefix2 = prefix + " "; for (int i = 0; i < size; i++) { pw.print(prefix); pw.print("session@"); pw.println(i); final ContentCaptureServerSession session = mSessions.valueAt(i); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index f59636b922784..ebe0083b398e4 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -17,7 +17,6 @@ package com.android.server.contentcapture; import android.annotation.NonNull; import android.content.ComponentName; -import android.content.Context; import android.os.IBinder; import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.SnapshotData; @@ -28,15 +27,13 @@ import android.view.contentcapture.ContentCaptureSessionId; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; -import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; import java.io.PrintWriter; -final class ContentCaptureServerSession implements ContentCaptureServiceCallbacks { +final class ContentCaptureServerSession { private static final String TAG = ContentCaptureServerSession.class.getSimpleName(); - private final Object mLock; final IBinder mActivityToken; private final ContentCapturePerUserService mService; private final RemoteContentCaptureService mRemoteService; @@ -52,19 +49,16 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback */ private final int mUid; - ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock, - @NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service, - @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName, - int taskId, int displayId, @NonNull String sessionId, int uid, int flags, - boolean bindInstantServiceAllowed, boolean verbose) { - mLock = lock; + ContentCaptureServerSession(@NonNull IBinder activityToken, + @NonNull ContentCapturePerUserService service, + @NonNull RemoteContentCaptureService remoteService, + @NonNull ComponentName appComponentName, + int taskId, int displayId, @NonNull String sessionId, int uid, int flags) { mActivityToken = activityToken; mService = service; mId = Preconditions.checkNotNull(sessionId); mUid = uid; - mRemoteService = new RemoteContentCaptureService(context, - ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this, - bindInstantServiceAllowed, verbose); + mRemoteService = remoteService; mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null, appComponentName, taskId, displayId, flags); } @@ -126,17 +120,6 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback } } - @Override // from RemoteContentCaptureServiceCallbacks - public void onServiceDied(@NonNull RemoteContentCaptureService service) { - // TODO(b/111276913): implement (remove session from PerUserSession?) - if (mService.isDebug()) { - Slog.d(TAG, "onServiceDied() for " + mId); - } - synchronized (mLock) { - removeSelfLocked(/* notifyRemoteService= */ true); - } - } - @GuardedBy("mLock") public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println(); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 942ee11aa4814..56ae87f25ebab 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -32,8 +32,6 @@ final class RemoteContentCaptureService extends AbstractMultiplePendingRequestsRemoteService { - // TODO(b/117779333): changed it so it's permanentely bound - private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; RemoteContentCaptureService(Context context, String serviceInterface, @@ -44,19 +42,18 @@ final class RemoteContentCaptureService bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2); } - @Override // from RemoteService + @Override // from AbstractRemoteService protected IContentCaptureService getServiceInterface(@NonNull IBinder service) { return IContentCaptureService.Stub.asInterface(service); } - // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative - @Override // from RemoteService + @Override // from AbstractRemoteService protected long getTimeoutIdleBindMillis() { // TODO(b/111276913): read from Settings so it can be changed in the field - return TIMEOUT_IDLE_BIND_MILLIS; + return PERMANENT_BOUND_TIMEOUT_MS; } - @Override // from RemoteService + @Override // from AbstractRemoteService protected long getRemoteRequestMillis() { // TODO(b/111276913): read from Settings so it can be changed in the field return TIMEOUT_REMOTE_REQUEST_MILLIS; diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index 7562f0f713fdd..a3ebe2408fa35 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -38,6 +38,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.AbstractRemoteService; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; @@ -218,6 +219,20 @@ public abstract class AbstractMasterSystemServiceTypically called by subclasses when creating {@link AbstractRemoteService} instances. + * + *

NOTE: must not be called by {@code ShellCommand} as it does not check for + * permission. + */ + public final boolean isBindInstantServiceAllowed() { + synchronized (mLock) { + return mAllowInstantService; + } + } + /** * Sets whether the service is allowed to bind to an instant-app. * @@ -314,6 +329,7 @@ public abstract class AbstractMasterSystemServiceMUST be overridden by subclasses that bind to an {@link AbstractRemoteService}. + *

MUST be overridden by subclasses that bind to an + * {@link com.android.internal.infra.AbstractRemoteService}. * * @throws NameNotFoundException if the service does not exist. * @throws SecurityException if the service does not have the proper permissions to be bound to. @@ -89,7 +90,7 @@ public abstract class AbstractPerUserSystemService