Refactored InteractionSessionId.

Rather than use a local and global id, just use a local, unique id.

Such change not only simplifies the code, but makes it consistent with
TextClassificationSessionId.

Test: mmm -j packages/experimental/FillService && \
      adb install -r ${OUT}/data/app/FillService/FillService.apk && \
      adb shell settings put secure intel_service foo.bar.fill/.AiaiService
Bug: 111276913

Change-Id: I0e610f825aab1d2b32bbafa1bd3d3c7897d889d6
diff --git a/core/java/android/service/intelligence/InteractionSessionId.java b/core/java/android/service/intelligence/InteractionSessionId.java
index ca68f8e..a2971ae 100644
--- a/core/java/android/service/intelligence/InteractionSessionId.java
+++ b/core/java/android/service/intelligence/InteractionSessionId.java
@@ -16,43 +16,85 @@
 
 package android.service.intelligence;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.io.PrintWriter;
+import java.util.UUID;
 
 // TODO(b/111276913): add javadocs / implement equals/hashcode/string
 /** @hide */
 @SystemApi
 public final class InteractionSessionId implements Parcelable {
 
-    private final int mGlobalId;
+    private final @NonNull String mValue;
 
-    // TODO(b/111276913): remove if not needed
-    private final int mLocalId;
-
-    /** @hide */
-    public InteractionSessionId(int globalId, int localId) {
-        mGlobalId = globalId;
-        mLocalId = localId;
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public InteractionSessionId() {
+        this(UUID.randomUUID().toString());
     }
 
-    /** @hide */
-    public int getGlobalId() {
-        return mGlobalId;
+    /**
+     * Creates a new instance.
+     *
+     * @param value The internal value.
+     *
+     * @hide
+     */
+    public InteractionSessionId(@NonNull String value) {
+        mValue = value;
+    }
+
+    /**
+     * @hide
+     */
+    public String getValue() {
+        return mValue;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mValue == null) ? 0 : mValue.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final InteractionSessionId other = (InteractionSessionId) obj;
+        if (mValue == null) {
+            if (other.mValue != null) return false;
+        } else if (!mValue.equals(other.mValue)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @inheritdoc
+     *
+     * <p><b>NOTE: </b>this method is only useful for debugging purposes and is not guaranteed to
+     * be stable, hence it should not be used to identify the session.
+     */
+    @Override
+    public String toString() {
+        return mValue;
     }
 
     /** @hide */
     // TODO(b/111276913): dump to proto as well
     public void dump(PrintWriter pw) {
-        pw.print("globalId="); pw.print(mGlobalId);
-        pw.print("localId="); pw.print(mLocalId);
-    }
-
-    @Override
-    public String toString() {
-        return "SessionId[globalId=" + mGlobalId + ", localId=" + mLocalId + "]";
+        pw.print(mValue);
     }
 
     @Override
@@ -62,8 +104,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(mGlobalId);
-        parcel.writeInt(mLocalId);
+        parcel.writeString(mValue);
     }
 
     public static final Parcelable.Creator<InteractionSessionId> CREATOR =
@@ -71,9 +112,7 @@
 
         @Override
         public InteractionSessionId createFromParcel(Parcel parcel) {
-            final int globalId = parcel.readInt();
-            final int localId = parcel.readInt();
-            return new InteractionSessionId(globalId, localId);
+            return new InteractionSessionId(parcel.readString());
         }
 
         @Override
diff --git a/core/java/android/view/intelligence/IIntelligenceManager.aidl b/core/java/android/view/intelligence/IIntelligenceManager.aidl
index 7bbe99a..2f128de 100644
--- a/core/java/android/view/intelligence/IIntelligenceManager.aidl
+++ b/core/java/android/view/intelligence/IIntelligenceManager.aidl
@@ -17,9 +17,8 @@
 package android.view.intelligence;
 
 import android.content.ComponentName;
-
 import android.os.IBinder;
-
+import android.service.intelligence.InteractionSessionId;
 import android.view.intelligence.ContentCaptureEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -34,19 +33,16 @@
       * Starts a session, sending the "remote" sessionId to the receiver.
       */
     void startSession(int userId, IBinder activityToken, in ComponentName componentName,
-                      int localSessionId, int flags, in IResultReceiver result);
+                      in InteractionSessionId sessionId, int flags, in IResultReceiver result);
 
     /**
       * Finishes a session.
       */
-    // TODO(b/111276913): pass just (global) session id
-    void finishSession(int userId, IBinder activityToken, in ComponentName componentName,
-                       int localSessionId, int globalSessionId);
+    void finishSession(int userId, in InteractionSessionId sessionId);
 
     /**
       * Sends a batch of events
       */
-    // TODO(b/111276913): pass just (global) session id
-    void sendEvents(int userId, IBinder activityToken, in ComponentName componentName,
-                    int localSessionId, int globalSessionId, in List<ContentCaptureEvent> events);
+    void sendEvents(int userId, in InteractionSessionId sessionId,
+                    in List<ContentCaptureEvent> events);
 }
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index a30d77e..9bf6c2c 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.service.intelligence.InteractionSessionId;
 import android.util.Log;
 import android.view.intelligence.ContentCaptureEvent.EventType;
 
@@ -55,9 +56,6 @@
     public static final int FLAG_USER_INPUT = 0x1;
 
 
-    /** @hide */
-    public static final int NO_SESSION = 0;
-
     /**
      * Initial state, when there is no session.
      *
@@ -66,11 +64,11 @@
     public static final int STATE_UNKNOWN = 0;
 
     /**
-     * Service's startSession() was called, but remote session id was not returned yet.
+     * Service's startSession() was called, but server didn't confirm it was created yet.
      *
      * @hide
      */
-    public static final int STATE_WAITING_FOR_SESSION_ID = 1;
+    public static final int STATE_WAITING_FOR_SERVER = 1;
 
     /**
      * Session is active.
@@ -79,8 +77,6 @@
      */
     public static final int STATE_ACTIVE = 2;
 
-    private static int sNextSessionId;
-
     private final Context mContext;
 
     @Nullable
@@ -88,14 +84,9 @@
 
     private final Object mLock = new Object();
 
-    // TODO(b/111276913): localSessionId might be an overkill, perhaps just the global id is enough.
-    // Let's keep both for now, and revisit once we decide whether the session id will be persisted
-    // when the activity's process is killed
+    @Nullable
     @GuardedBy("mLock")
-    private int mLocalSessionId = NO_SESSION;
-
-    @GuardedBy("mLock")
-    private int mRemoteSessionId = NO_SESSION;
+    private InteractionSessionId mId;
 
     @GuardedBy("mLock")
     private int mState = STATE_UNKNOWN;
@@ -124,27 +115,25 @@
                         + getStateAsStringLocked());
                 return;
             }
-            mState = STATE_WAITING_FOR_SESSION_ID;
-            mLocalSessionId = ++sNextSessionId;
-            mRemoteSessionId = NO_SESSION;
+            mState = STATE_WAITING_FOR_SERVER;
+            mId = new InteractionSessionId();
             mApplicationToken = token;
             mComponentName = componentName;
 
             if (VERBOSE) {
                 Log.v(TAG, "onActivityStarted(): token=" + token + ", act=" + componentName
-                        + ", localSessionId=" + mLocalSessionId);
+                        + ", id=" + mId);
             }
             final int flags = 0; // TODO(b/111276913): get proper flags
 
             try {
                 mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
-                        mLocalSessionId, flags, new IResultReceiver.Stub() {
+                        mId, flags, new IResultReceiver.Stub() {
                             @Override
                             public void send(int resultCode, Bundle resultData)
                                     throws RemoteException {
                                 synchronized (mLock) {
                                     if (resultCode > 0) {
-                                        mRemoteSessionId = resultCode;
                                         mState = STATE_ACTIVE;
                                     } else {
                                         // TODO(b/111276913): handle other cases like disabled by
@@ -153,7 +142,7 @@
                                     }
                                     if (VERBOSE) {
                                         Log.v(TAG, "onActivityStarted() result: code=" + resultCode
-                                                + ", remoteSession=" + mRemoteSessionId
+                                                + ", id=" + mId
                                                 + ", state=" + getStateAsStringLocked());
                                     }
                                 }
@@ -189,8 +178,7 @@
             }
 
             try {
-                mService.sendEvents(mContext.getUserId(), mApplicationToken, mComponentName,
-                        mLocalSessionId, mRemoteSessionId, events);
+                mService.sendEvents(mContext.getUserId(), mId, events);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -207,15 +195,13 @@
 
             if (VERBOSE) {
                 Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsStringLocked()
-                        + ", localSessionId=" + mLocalSessionId
-                        + ", mRemoteSessionId=" + mRemoteSessionId);
+                        + ", mId=" + mId);
             }
 
             try {
-                mService.finishSession(mContext.getUserId(), mApplicationToken, mComponentName,
-                        mLocalSessionId, mRemoteSessionId);
+                mService.finishSession(mContext.getUserId(), mId);
                 mState = STATE_UNKNOWN;
-                mLocalSessionId = mRemoteSessionId = NO_SESSION;
+                mId = null;
                 mApplicationToken = null;
                 mComponentName = null;
             } catch (RemoteException e) {
@@ -334,12 +320,11 @@
             pw.print(prefix2); pw.print("mService: "); pw.println(mService);
             pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
             pw.print(prefix2); pw.print("enabled: "); pw.println(isContentCaptureEnabled());
-            pw.print(prefix2); pw.print("mLocalSessionId: "); pw.println(mLocalSessionId);
-            pw.print(prefix2); pw.print("mRemoteSessionId: "); pw.println(mRemoteSessionId);
-            pw.print(prefix2); pw.print("mState: "); pw.print(mState); pw.print(" (");
+            pw.print(prefix2); pw.print("id: "); pw.println(mId);
+            pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
             pw.print(getStateAsStringLocked()); pw.println(")");
-            pw.print(prefix2); pw.print("mAppToken: "); pw.println(mApplicationToken);
-            pw.print(prefix2); pw.print("mComponentName: "); pw.println(mComponentName);
+            pw.print(prefix2); pw.print("appToken: "); pw.println(mApplicationToken);
+            pw.print(prefix2); pw.print("componentName: "); pw.println(mComponentName);
         }
     }
 
@@ -353,8 +338,8 @@
         switch (state) {
             case STATE_UNKNOWN:
                 return "UNKNOWN";
-            case STATE_WAITING_FOR_SESSION_ID:
-                return "WAITING_FOR_SESSION_ID";
+            case STATE_WAITING_FOR_SERVER:
+                return "WAITING_FOR_SERVER";
             case STATE_ACTIVE:
                 return "ACTIVE";
             default:
diff --git a/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
index 14b28d3..9cab1ed 100644
--- a/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
+++ b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -26,6 +26,7 @@
 import android.view.intelligence.ContentCaptureEvent;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 import com.android.server.AbstractRemoteService;
 import com.android.server.intelligence.RemoteIntelligenceService.RemoteIntelligenceServiceCallbacks;
 
@@ -47,15 +48,15 @@
     ContentCaptureSession(@NonNull Context context, int userId, @NonNull Object lock,
             @NonNull IBinder activityToken, @NonNull IntelligencePerUserService service,
             @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName,
-            int taskId, int displayId, int localSessionId, int globalSessionId, int flags,
+            int taskId, int displayId, @NonNull InteractionSessionId sessionId, int flags,
             boolean bindInstantServiceAllowed, boolean verbose) {
         mLock = lock;
         mActivityToken = activityToken;
         mService = service;
+        mId = Preconditions.checkNotNull(sessionId);
         mRemoteService = new RemoteIntelligenceService(context,
                 IntelligenceService.SERVICE_INTERFACE, serviceComponentName, userId, this,
                 bindInstantServiceAllowed, verbose);
-        mId = new InteractionSessionId(globalSessionId, localSessionId);
         mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags);
     }
 
@@ -88,7 +89,7 @@
                 mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
             }
         } finally {
-            mService.removeSessionLocked(mInterationContext.getActivityComponent());
+            mService.removeSessionLocked(mId);
         }
     }
 
@@ -114,17 +115,15 @@
         }
     }
 
-    /**
-     * Gets global id, unique per {@link IntelligencePerUserService}.
-     */
-    public int getGlobalSessionId() {
-        return mId.getGlobalId();
-    }
-
     @GuardedBy("mLock")
     public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
         pw.print(prefix); pw.print("id: ");  mId.dump(pw); pw.println();
         pw.print(prefix); pw.print("context: ");  mInterationContext.dump(pw); pw.println();
         pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
     }
+
+    @Override
+    public String toString() {
+        return "ContentCaptureSession[id=" + mId.getValue() + ", act=" + mActivityToken + "]";
+    }
 }
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
index d67c15b..43d4a44 100644
--- a/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.os.IBinder;
 import android.os.UserManager;
+import android.service.intelligence.InteractionSessionId;
 import android.view.intelligence.ContentCaptureEvent;
 import android.view.intelligence.IIntelligenceManager;
 
@@ -87,9 +88,11 @@
 
         @Override
         public void startSession(int userId, @NonNull IBinder activityToken,
-                @NonNull ComponentName componentName, int localSessionId, int flags,
-                @NonNull IResultReceiver result) {
+                @NonNull ComponentName componentName, @NonNull InteractionSessionId sessionId,
+                int flags, @NonNull IResultReceiver result) {
             Preconditions.checkNotNull(activityToken);
+            Preconditions.checkNotNull(componentName);
+            Preconditions.checkNotNull(sessionId);
 
             // TODO(b/111276913): refactor getTaskIdForActivity() to also return ComponentName,
             // so we don't pass it on startSession (same for Autofill)
@@ -101,31 +104,29 @@
             synchronized (mLock) {
                 final IntelligencePerUserService service = getServiceForUserLocked(userId);
                 service.startSessionLocked(activityToken, componentName, taskId, displayId,
-                        localSessionId, flags, result);
+                        sessionId, flags, result);
             }
         }
 
         @Override
-        public void sendEvents(int userId, @NonNull IBinder activityToken,
-                @NonNull ComponentName componentName, int localSessionId, int globalSessionId,
-                List<ContentCaptureEvent> events) {
+        public void sendEvents(int userId, @NonNull InteractionSessionId sessionId,
+                @NonNull List<ContentCaptureEvent> events) {
+            Preconditions.checkNotNull(sessionId);
             Preconditions.checkNotNull(events);
 
             synchronized (mLock) {
                 final IntelligencePerUserService service = getServiceForUserLocked(userId);
-                service.sendEventsLocked(componentName, events);
+                service.sendEventsLocked(sessionId, events);
             }
         }
 
         @Override
-        public void finishSession(int userId, @NonNull IBinder activityToken,
-                @NonNull ComponentName componentName, int localSessionId, int globalSessionId) {
-            Preconditions.checkNotNull(activityToken);
+        public void finishSession(int userId, @NonNull InteractionSessionId sessionId) {
+            Preconditions.checkNotNull(sessionId);
 
             synchronized (mLock) {
                 final IntelligencePerUserService service = getServiceForUserLocked(userId);
-                service.finishSessionLocked(activityToken, componentName, localSessionId,
-                        globalSessionId);
+                service.finishSessionLocked(sessionId);
             }
         }
 
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
index 1d98819..584b872 100644
--- a/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -25,9 +25,11 @@
 import android.content.pm.ServiceInfo;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.intelligence.InteractionSessionId;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.intelligence.ContentCaptureEvent;
+import android.view.intelligence.IntelligenceManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -44,11 +46,9 @@
 
     private static final String TAG = "IntelligencePerUserService";
 
-    private static int sNextSessionId;
-
-    // TODO(b/111276913): should key by componentName + taskId or ActivityToken
     @GuardedBy("mLock")
-    private final ArrayMap<ComponentName, ContentCaptureSession> mSessions = new ArrayMap<>();
+    private final ArrayMap<InteractionSessionId, ContentCaptureSession> mSessions =
+            new ArrayMap<>();
 
     // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
 
@@ -85,8 +85,9 @@
     // TODO(b/111276913): log metrics
     @GuardedBy("mLock")
     public void startSessionLocked(@NonNull IBinder activityToken,
-            @NonNull ComponentName componentName, int taskId, int displayId, int localSessionId,
-            int flags, @NonNull IResultReceiver resultReceiver) {
+            @NonNull ComponentName componentName, int taskId, int displayId,
+            @NonNull InteractionSessionId sessionId, int flags,
+            @NonNull IResultReceiver resultReceiver) {
         final ComponentName serviceComponentName = getServiceComponentName();
         if (serviceComponentName == null) {
             // TODO(b/111276913): this happens when the system service is starting, we should
@@ -96,16 +97,16 @@
             return;
         }
 
-        ContentCaptureSession session = mSessions.get(componentName);
+        ContentCaptureSession session = mSessions.get(sessionId);
         if (session != null) {
             if (mMaster.debug) {
-                Slog.d(TAG, "startSession(): reusing session " + session.getGlobalSessionId()
-                        + " for " + componentName);
+                Slog.d(TAG, "startSession(): reusing session " + sessionId + " for "
+                        + componentName);
             }
             // TODO(b/111276913): check if local ids match and decide what to do if they don't
             // TODO(b/111276913): should we call session.notifySessionStartedLocked() again??
             // if not, move notifySessionStartedLocked() into session constructor
-            sendToClient(resultReceiver, session.getGlobalSessionId());
+            sendToClient(resultReceiver, IntelligenceManager.STATE_ACTIVE);
             return;
         }
 
@@ -113,59 +114,53 @@
         final boolean bindInstantServiceAllowed = false;
 
         session = new ContentCaptureSession(getContext(), mUserId, mLock, activityToken,
-                this, serviceComponentName, componentName, taskId, displayId, localSessionId,
-                ++sNextSessionId, flags, bindInstantServiceAllowed, mMaster.verbose);
+                this, serviceComponentName, componentName, taskId, displayId, sessionId, flags,
+                bindInstantServiceAllowed, mMaster.verbose);
         if (mMaster.verbose) {
-            Slog.v(TAG, "startSession(): new session for " + componentName + "; globalId ="
-                    + session.getGlobalSessionId());
+            Slog.v(TAG, "startSession(): new session for " + componentName + " and id "
+                    + sessionId);
         }
-        mSessions.put(componentName, session);
+        mSessions.put(sessionId, session);
         session.notifySessionStartedLocked();
-        sendToClient(resultReceiver, session.getGlobalSessionId());
+        sendToClient(resultReceiver, IntelligenceManager.STATE_ACTIVE);
     }
 
     // TODO(b/111276913): log metrics
     @GuardedBy("mLock")
-    public void finishSessionLocked(@NonNull IBinder activityToken,
-            @NonNull ComponentName componentName, int localSessionId, int globalSessionId) {
-        final ContentCaptureSession session = mSessions.get(componentName);
+    public void finishSessionLocked(@NonNull InteractionSessionId sessionId) {
+        final ContentCaptureSession session = mSessions.get(sessionId);
         if (session == null) {
-            Slog.w(TAG, "finishSession(): no session for " + componentName);
+            Slog.w(TAG, "finishSession(): no session with id" + sessionId);
             return;
         }
         if (mMaster.verbose) {
-            Slog.v(TAG, "finishSession(): comp=" + componentName + "; globalId ="
-                    + session.getGlobalSessionId());
+            Slog.v(TAG, "finishSession(): " + session);
         }
-        // TODO(b/111276913): check if all arguments match existing session and throw exception if
-        // not. Or just use componentName if we change AIDL to pass just ApplicationToken and
-        // retrieve componentName from AMInternal
         session.removeSelfLocked(true);
     }
 
     @GuardedBy("mLock")
-    public void sendEventsLocked(@NonNull ComponentName componentName,
+    public void sendEventsLocked(@NonNull InteractionSessionId sessionId,
             @NonNull List<ContentCaptureEvent> events) {
-        final ContentCaptureSession session = mSessions.get(componentName);
+        final ContentCaptureSession session = mSessions.get(sessionId);
         if (session == null) {
-            Slog.w(TAG, "sendEventsLocked(): no session for " + componentName);
+            Slog.w(TAG, "sendEvents(): no session for " + sessionId);
             return;
         }
         if (mMaster.verbose) {
-            Slog.v(TAG, "sendEventsLocked(): comp=" + componentName + "; events =" + events.size());
+            Slog.v(TAG, "sendEvents(): id=" + sessionId + "; events =" + events.size());
         }
         session.sendEventsLocked(events);
     }
 
     @GuardedBy("mLock")
-    public void removeSessionLocked(@NonNull ComponentName key) {
-        mSessions.remove(key);
+    public void removeSessionLocked(@NonNull InteractionSessionId sessionId) {
+        mSessions.remove(sessionId);
     }
 
     @Override
     protected void dumpLocked(String prefix, PrintWriter pw) {
         super.dumpLocked(prefix, pw);
-        pw.print(prefix); pw.print("next id: "); pw.println(sNextSessionId);
         if (mSessions.isEmpty()) {
             pw.print(prefix); pw.println("no sessions");
         } else {
@@ -187,5 +182,4 @@
             Slog.w(TAG, "Error async reporting result to client: " + e);
         }
     }
-
 }