adpf: Use one client token per process for all hint sessions

So it doesn't leak when gc is delayed in service process.

Update HintManagerService to allow multiple sessions tied to the same
token.

Bug: 218129784
Test: chrome no longer gets killed
Change-Id: I67a66041cc67d01e4cfcd3ded303a1bed6050f60
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 0c36051..65428de 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -48,6 +48,7 @@
     static APerformanceHintManager* create(sp<IHintManager> iHintManager);
 
     sp<IHintManager> mHintManager;
+    const sp<IBinder> mToken = sp<BBinder>::make();
     const int64_t mPreferredRateNanos;
 };
 
@@ -119,11 +120,10 @@
 
 APerformanceHintSession* APerformanceHintManager::createSession(
         const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
-    sp<IBinder> token = sp<BBinder>::make();
     std::vector<int32_t> tids(threadIds, threadIds + size);
     sp<IHintSession> session;
     binder::Status ret =
-            mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+            mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session);
     if (!ret.isOk() || !session) {
         return nullptr;
     }
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2491565d..8755662 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -51,8 +52,13 @@
     private static final boolean DEBUG = false;
     @VisibleForTesting final long mHintSessionPreferredRate;
 
+    // Multi-levle map storing all active AppHintSessions.
+    // First level is keyed by the UID of the client process creating the session.
+    // Second level is keyed by an IBinder passed from client process. This is used to observe
+    // when the process exits. The client generally uses the same IBinder object across multiple
+    // sessions, so the value is a set of AppHintSessions.
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions;
+    private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
 
     /** Lock to protect HAL handles and listen list. */
     private final Object mLock = new Object();
@@ -201,13 +207,16 @@
         public void onUidGone(int uid, boolean disabled) {
             FgThread.getHandler().post(() -> {
                 synchronized (mLock) {
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
                         // Will remove the session from tokenMap
-                        tokenMap.valueAt(i).close();
+                        ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+                        for (int j = sessionSet.size() - 1; j >= 0; j--) {
+                            sessionSet.valueAt(j).close();
+                        }
                     }
                     mProcStatesCache.delete(uid);
                 }
@@ -231,12 +240,14 @@
             FgThread.getHandler().post(() -> {
                 synchronized (mLock) {
                     mProcStatesCache.put(uid, procState);
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
-                    for (AppHintSession s : tokenMap.values()) {
-                        s.onProcStateChanged();
+                    for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
+                        for (AppHintSession s : sessionSet) {
+                            s.onProcStateChanged();
+                        }
                     }
                 }
             });
@@ -305,17 +316,25 @@
 
                 long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
                         tids, durationNanos);
-                if (halSessionPtr == 0) return null;
+                if (halSessionPtr == 0) {
+                    return null;
+                }
 
                 AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
                         halSessionPtr, durationNanos);
                 synchronized (mLock) {
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(callingUid);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+                            mActiveSessions.get(callingUid);
                     if (tokenMap == null) {
                         tokenMap = new ArrayMap<>(1);
                         mActiveSessions.put(callingUid, tokenMap);
                     }
-                    tokenMap.put(token, hs);
+                    ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
+                    if (sessionSet == null) {
+                        sessionSet = new ArraySet<>(1);
+                        tokenMap.put(token, sessionSet);
+                    }
+                    sessionSet.add(hs);
                     return hs;
                 }
             } finally {
@@ -339,10 +358,14 @@
                 pw.println("Active Sessions:");
                 for (int i = 0; i < mActiveSessions.size(); i++) {
                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
-                    ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i);
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+                            mActiveSessions.valueAt(i);
                     for (int j = 0; j < tokenMap.size(); j++) {
-                        pw.println("  Session " + j + ":");
-                        tokenMap.valueAt(j).dump(pw, "    ");
+                        ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+                        for (int k = 0; k < sessionSet.size(); ++k) {
+                            pw.println("  Session:");
+                            sessionSet.valueAt(k).dump(pw, "    ");
+                        }
                     }
                 }
             }
@@ -432,11 +455,18 @@
                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
                 mHalSessionPtr = 0;
                 mToken.unlinkToDeath(this, 0);
-                ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid);
+                ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
                 if (tokenMap == null) {
-                    Slogf.w(TAG, "UID %d is note present in active session map", mUid);
+                    Slogf.w(TAG, "UID %d is not present in active session map", mUid);
+                    return;
                 }
-                tokenMap.remove(mToken);
+                ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
+                if (sessionSet == null) {
+                    Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
+                    return;
+                }
+                sessionSet.remove(this);
+                if (sessionSet.isEmpty()) tokenMap.remove(mToken);
                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
             }
         }