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 AbstractRemoteService 0) {
+ pw.append(" (unbind in : ");
+ TimeUtils.formatDuration(mNextUnbind - SystemClock.elapsedRealtime(), pw);
+ pw.append(")");
+ } else {
+ pw.append(" (permanently bound)");
+ }
+ }
+ pw.println();
pw.append(prefix).append("mBindInstantServiceAllowed=").println(mBindInstantServiceAllowed);
pw.append(prefix).append("idleTimeout=")
- .append(Long.toString(getTimeoutIdleBindMillis() / 1000)).append("s").println();
+ .append(Long.toString(idleTimeout / 1000)).append("s").println();
pw.append(prefix).append("requestTimeout=")
.append(Long.toString(getRemoteRequestMillis() / 1000)).append("s").println();
pw.println();
@@ -236,6 +256,8 @@ 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 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 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 AbstractMasterSystemService 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 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