1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.power.hint;
18 
19 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
20 
21 import android.annotation.NonNull;
22 import android.app.ActivityManager;
23 import android.app.ActivityManagerInternal;
24 import android.app.StatsManager;
25 import android.app.UidObserver;
26 import android.content.Context;
27 import android.os.Binder;
28 import android.os.IBinder;
29 import android.os.IHintManager;
30 import android.os.IHintSession;
31 import android.os.PerformanceHintManager;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.SystemProperties;
35 import android.util.ArrayMap;
36 import android.util.ArraySet;
37 import android.util.SparseArray;
38 import android.util.StatsEvent;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.DumpUtils;
43 import com.android.internal.util.FrameworkStatsLog;
44 import com.android.internal.util.Preconditions;
45 import com.android.server.FgThread;
46 import com.android.server.LocalServices;
47 import com.android.server.SystemService;
48 import com.android.server.utils.Slogf;
49 
50 import java.io.FileDescriptor;
51 import java.io.PrintWriter;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Objects;
55 
56 /** An hint service implementation that runs in System Server process. */
57 public final class HintManagerService extends SystemService {
58     private static final String TAG = "HintManagerService";
59     private static final boolean DEBUG = false;
60     @VisibleForTesting final long mHintSessionPreferredRate;
61 
62     // Multi-level map storing all active AppHintSessions.
63     // First level is keyed by the UID of the client process creating the session.
64     // Second level is keyed by an IBinder passed from client process. This is used to observe
65     // when the process exits. The client generally uses the same IBinder object across multiple
66     // sessions, so the value is a set of AppHintSessions.
67     @GuardedBy("mLock")
68     private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
69 
70     /** Lock to protect HAL handles and listen list. */
71     private final Object mLock = new Object();
72 
73     @VisibleForTesting final MyUidObserver mUidObserver;
74 
75     private final NativeWrapper mNativeWrapper;
76 
77     private final ActivityManagerInternal mAmInternal;
78 
79     private final Context mContext;
80 
81     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
82     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
83 
84     @VisibleForTesting final IHintManager.Stub mService = new BinderService();
85 
HintManagerService(Context context)86     public HintManagerService(Context context) {
87         this(context, new Injector());
88     }
89 
90     @VisibleForTesting
HintManagerService(Context context, Injector injector)91     HintManagerService(Context context, Injector injector) {
92         super(context);
93         mContext = context;
94         mActiveSessions = new ArrayMap<>();
95         mNativeWrapper = injector.createNativeWrapper();
96         mNativeWrapper.halInit();
97         mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
98         mUidObserver = new MyUidObserver();
99         mAmInternal = Objects.requireNonNull(
100                 LocalServices.getService(ActivityManagerInternal.class));
101     }
102 
103     @VisibleForTesting
104     static class Injector {
createNativeWrapper()105         NativeWrapper createNativeWrapper() {
106             return new NativeWrapper();
107         }
108     }
109 
isHalSupported()110     private boolean isHalSupported() {
111         return mHintSessionPreferredRate != -1;
112     }
113 
114     @Override
onStart()115     public void onStart() {
116         publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService);
117     }
118 
119     @Override
onBootPhase(int phase)120     public void onBootPhase(int phase) {
121         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
122             systemReady();
123         }
124         if (phase == SystemService.PHASE_BOOT_COMPLETED) {
125             registerStatsCallbacks();
126         }
127     }
128 
systemReady()129     private void systemReady() {
130         Slogf.v(TAG, "Initializing HintManager service...");
131         try {
132             ActivityManager.getService().registerUidObserver(mUidObserver,
133                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
134                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
135         } catch (RemoteException e) {
136             // ignored; both services live in system_server
137         }
138 
139     }
140 
registerStatsCallbacks()141     private void registerStatsCallbacks() {
142         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
143         statsManager.setPullAtomCallback(
144                 FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
145                 null, // use default PullAtomMetadata values
146                 DIRECT_EXECUTOR,
147                 this::onPullAtom);
148     }
149 
onPullAtom(int atomTag, @NonNull List<StatsEvent> data)150     private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
151         if (atomTag == FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO) {
152             final boolean isSurfaceFlingerUsingCpuHint =
153                     SystemProperties.getBoolean(PROPERTY_SF_ENABLE_CPU_HINT, false);
154             final boolean isHwuiHintManagerEnabled =
155                     SystemProperties.getBoolean(PROPERTY_HWUI_ENABLE_HINT_MANAGER, false);
156 
157             data.add(FrameworkStatsLog.buildStatsEvent(
158                     FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
159                     isSurfaceFlingerUsingCpuHint,
160                     isHwuiHintManagerEnabled));
161         }
162         return android.app.StatsManager.PULL_SUCCESS;
163     }
164 
165     /**
166      * Wrapper around the static-native methods from native.
167      *
168      * This class exists to allow us to mock static native methods in our tests. If mocking static
169      * methods becomes easier than this in the future, we can delete this class.
170      */
171     @VisibleForTesting
172     public static class NativeWrapper {
nativeInit()173         private native void nativeInit();
174 
nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)175         private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
176                 long durationNanos);
177 
nativePauseHintSession(long halPtr)178         private static native void nativePauseHintSession(long halPtr);
179 
nativeResumeHintSession(long halPtr)180         private static native void nativeResumeHintSession(long halPtr);
181 
nativeCloseHintSession(long halPtr)182         private static native void nativeCloseHintSession(long halPtr);
183 
nativeUpdateTargetWorkDuration( long halPtr, long targetDurationNanos)184         private static native void nativeUpdateTargetWorkDuration(
185                 long halPtr, long targetDurationNanos);
186 
nativeReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)187         private static native void nativeReportActualWorkDuration(
188                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
189 
nativeSendHint(long halPtr, int hint)190         private static native void nativeSendHint(long halPtr, int hint);
191 
nativeSetThreads(long halPtr, int[] tids)192         private static native void nativeSetThreads(long halPtr, int[] tids);
193 
nativeGetHintSessionPreferredRate()194         private static native long nativeGetHintSessionPreferredRate();
195 
196         /** Wrapper for HintManager.nativeInit */
halInit()197         public void halInit() {
198             nativeInit();
199         }
200 
201         /** Wrapper for HintManager.nativeCreateHintSession */
halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)202         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
203             return nativeCreateHintSession(tgid, uid, tids, durationNanos);
204         }
205 
206         /** Wrapper for HintManager.nativePauseHintSession */
halPauseHintSession(long halPtr)207         public void halPauseHintSession(long halPtr) {
208             nativePauseHintSession(halPtr);
209         }
210 
211         /** Wrapper for HintManager.nativeResumeHintSession */
halResumeHintSession(long halPtr)212         public void halResumeHintSession(long halPtr) {
213             nativeResumeHintSession(halPtr);
214         }
215 
216         /** Wrapper for HintManager.nativeCloseHintSession */
halCloseHintSession(long halPtr)217         public void halCloseHintSession(long halPtr) {
218             nativeCloseHintSession(halPtr);
219         }
220 
221         /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */
halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos)222         public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
223             nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos);
224         }
225 
226         /** Wrapper for HintManager.nativeReportActualWorkDuration */
halReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)227         public void halReportActualWorkDuration(
228                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
229             nativeReportActualWorkDuration(halPtr, actualDurationNanos,
230                     timeStampNanos);
231         }
232 
233         /** Wrapper for HintManager.sendHint */
halSendHint(long halPtr, int hint)234         public void halSendHint(long halPtr, int hint) {
235             nativeSendHint(halPtr, hint);
236         }
237 
238         /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
halGetHintSessionPreferredRate()239         public long halGetHintSessionPreferredRate() {
240             return nativeGetHintSessionPreferredRate();
241         }
242 
243         /** Wrapper for HintManager.nativeSetThreads */
halSetThreads(long halPtr, int[] tids)244         public void halSetThreads(long halPtr, int[] tids) {
245             nativeSetThreads(halPtr, tids);
246         }
247     }
248 
249     @VisibleForTesting
250     final class MyUidObserver extends UidObserver {
251         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
252 
isUidForeground(int uid)253         public boolean isUidForeground(int uid) {
254             synchronized (mLock) {
255                 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
256                         <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
257             }
258         }
259 
260         @Override
onUidGone(int uid, boolean disabled)261         public void onUidGone(int uid, boolean disabled) {
262             FgThread.getHandler().post(() -> {
263                 synchronized (mLock) {
264                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
265                     if (tokenMap == null) {
266                         return;
267                     }
268                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
269                         // Will remove the session from tokenMap
270                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
271                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
272                             sessionSet.valueAt(j).close();
273                         }
274                     }
275                     mProcStatesCache.delete(uid);
276                 }
277             });
278         }
279 
280         /**
281          * The IUidObserver callback is called from the system_server, so it'll be a direct function
282          * call from ActivityManagerService. Do not do heavy logic here.
283          */
284         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)285         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
286             FgThread.getHandler().post(() -> {
287                 synchronized (mLock) {
288                     mProcStatesCache.put(uid, procState);
289                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
290                     if (tokenMap == null) {
291                         return;
292                     }
293                     for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
294                         for (AppHintSession s : sessionSet) {
295                             s.onProcStateChanged();
296                         }
297                     }
298                 }
299             });
300         }
301     }
302 
303     @VisibleForTesting
getBinderServiceInstance()304     IHintManager.Stub getBinderServiceInstance() {
305         return mService;
306     }
307 
checkTidValid(int uid, int tgid, int [] tids)308     private boolean checkTidValid(int uid, int tgid, int [] tids) {
309         // Make sure all tids belongs to the same UID (including isolated UID),
310         // tids can belong to different application processes.
311         List<Integer> isolatedPids = null;
312         for (int threadId : tids) {
313             final String[] procStatusKeys = new String[] {
314                     "Uid:",
315                     "Tgid:"
316             };
317             long[] output = new long[procStatusKeys.length];
318             Process.readProcLines("/proc/" + threadId + "/status", procStatusKeys, output);
319             int uidOfThreadId = (int) output[0];
320             int pidOfThreadId = (int) output[1];
321 
322             // use PID check for isolated processes, use UID check for non-isolated processes.
323             if (pidOfThreadId == tgid || uidOfThreadId == uid) {
324                 continue;
325             }
326             // Only call into AM if the tid is either isolated or invalid
327             if (isolatedPids == null) {
328                 // To avoid deadlock, do not call into AMS if the call is from system.
329                 if (uid == Process.SYSTEM_UID) {
330                     return false;
331                 }
332                 isolatedPids = mAmInternal.getIsolatedProcesses(uid);
333                 if (isolatedPids == null) {
334                     return false;
335                 }
336             }
337             if (isolatedPids.contains(pidOfThreadId)) {
338                 continue;
339             }
340             return false;
341         }
342         return true;
343     }
344 
345     @VisibleForTesting
346     final class BinderService extends IHintManager.Stub {
347         @Override
createHintSession(IBinder token, int[] tids, long durationNanos)348         public IHintSession createHintSession(IBinder token, int[] tids, long durationNanos) {
349             if (!isHalSupported()) return null;
350 
351             java.util.Objects.requireNonNull(token);
352             java.util.Objects.requireNonNull(tids);
353             Preconditions.checkArgument(tids.length != 0, "tids should"
354                     + " not be empty.");
355 
356             final int callingUid = Binder.getCallingUid();
357             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
358             final long identity = Binder.clearCallingIdentity();
359             try {
360                 if (!checkTidValid(callingUid, callingTgid, tids)) {
361                     throw new SecurityException("Some tid doesn't belong to the application");
362                 }
363 
364                 long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
365                         tids, durationNanos);
366                 if (halSessionPtr == 0) {
367                     return null;
368                 }
369 
370                 AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
371                         halSessionPtr, durationNanos);
372                 logPerformanceHintSessionAtom(callingUid, halSessionPtr, durationNanos, tids);
373                 synchronized (mLock) {
374                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
375                             mActiveSessions.get(callingUid);
376                     if (tokenMap == null) {
377                         tokenMap = new ArrayMap<>(1);
378                         mActiveSessions.put(callingUid, tokenMap);
379                     }
380                     ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
381                     if (sessionSet == null) {
382                         sessionSet = new ArraySet<>(1);
383                         tokenMap.put(token, sessionSet);
384                     }
385                     sessionSet.add(hs);
386                     return hs;
387                 }
388             } finally {
389                 Binder.restoreCallingIdentity(identity);
390             }
391         }
392 
393         @Override
getHintSessionPreferredRate()394         public long getHintSessionPreferredRate() {
395             return mHintSessionPreferredRate;
396         }
397 
398         @Override
setHintSessionThreads(@onNull IHintSession hintSession, @NonNull int[] tids)399         public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) {
400             AppHintSession appHintSession = (AppHintSession) hintSession;
401             appHintSession.setThreads(tids);
402         }
403 
404         @Override
getHintSessionThreadIds(@onNull IHintSession hintSession)405         public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) {
406             AppHintSession appHintSession = (AppHintSession) hintSession;
407             return appHintSession.getThreadIds();
408         }
409 
410         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)411         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
412             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
413                 return;
414             }
415             synchronized (mLock) {
416                 pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
417                 pw.println("HAL Support: " + isHalSupported());
418                 pw.println("Active Sessions:");
419                 for (int i = 0; i < mActiveSessions.size(); i++) {
420                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
421                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
422                             mActiveSessions.valueAt(i);
423                     for (int j = 0; j < tokenMap.size(); j++) {
424                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
425                         for (int k = 0; k < sessionSet.size(); ++k) {
426                             pw.println("  Session:");
427                             sessionSet.valueAt(k).dump(pw, "    ");
428                         }
429                     }
430                 }
431             }
432         }
433 
logPerformanceHintSessionAtom(int uid, long sessionId, long targetDuration, int[] tids)434         private void logPerformanceHintSessionAtom(int uid, long sessionId,
435                 long targetDuration, int[] tids) {
436             FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid,
437                     sessionId, targetDuration, tids.length);
438         }
439     }
440 
441     @VisibleForTesting
442     final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
443         protected final int mUid;
444         protected final int mPid;
445         protected int[] mThreadIds;
446         protected final IBinder mToken;
447         protected long mHalSessionPtr;
448         protected long mTargetDurationNanos;
449         protected boolean mUpdateAllowed;
450         protected int[] mNewThreadIds;
451 
AppHintSession( int uid, int pid, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos)452         protected AppHintSession(
453                 int uid, int pid, int[] threadIds, IBinder token,
454                 long halSessionPtr, long durationNanos) {
455             mUid = uid;
456             mPid = pid;
457             mToken = token;
458             mThreadIds = threadIds;
459             mHalSessionPtr = halSessionPtr;
460             mTargetDurationNanos = durationNanos;
461             mUpdateAllowed = true;
462             updateHintAllowed();
463             try {
464                 token.linkToDeath(this, 0);
465             } catch (RemoteException e) {
466                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
467                 throw new IllegalStateException("Client already dead", e);
468             }
469         }
470 
471         @VisibleForTesting
updateHintAllowed()472         boolean updateHintAllowed() {
473             synchronized (mLock) {
474                 final boolean allowed = mUidObserver.isUidForeground(mUid);
475                 if (allowed && !mUpdateAllowed) resume();
476                 if (!allowed && mUpdateAllowed) pause();
477                 mUpdateAllowed = allowed;
478                 return mUpdateAllowed;
479             }
480         }
481 
482         @Override
updateTargetWorkDuration(long targetDurationNanos)483         public void updateTargetWorkDuration(long targetDurationNanos) {
484             synchronized (mLock) {
485                 if (mHalSessionPtr == 0 || !updateHintAllowed()) {
486                     return;
487                 }
488                 Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
489                         + " the target duration to be greater than 0.");
490                 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
491                 mTargetDurationNanos = targetDurationNanos;
492             }
493         }
494 
495         @Override
reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos)496         public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
497             synchronized (mLock) {
498                 if (mHalSessionPtr == 0 || !updateHintAllowed()) {
499                     return;
500                 }
501                 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
502                         + " of hint durations shouldn't be 0.");
503                 Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length,
504                         "The length of durations and timestamps should be the same.");
505                 for (int i = 0; i < actualDurationNanos.length; i++) {
506                     if (actualDurationNanos[i] <= 0) {
507                         throw new IllegalArgumentException(
508                                 String.format("durations[%d]=%d should be greater than 0",
509                                         i, actualDurationNanos[i]));
510                     }
511                 }
512                 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos,
513                         timeStampNanos);
514             }
515         }
516 
517         /** TODO: consider monitor session threads and close session if any thread is dead. */
518         @Override
close()519         public void close() {
520             synchronized (mLock) {
521                 if (mHalSessionPtr == 0) return;
522                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
523                 mHalSessionPtr = 0;
524                 mToken.unlinkToDeath(this, 0);
525                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
526                 if (tokenMap == null) {
527                     Slogf.w(TAG, "UID %d is not present in active session map", mUid);
528                     return;
529                 }
530                 ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
531                 if (sessionSet == null) {
532                     Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
533                     return;
534                 }
535                 sessionSet.remove(this);
536                 if (sessionSet.isEmpty()) tokenMap.remove(mToken);
537                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
538             }
539         }
540 
541         @Override
sendHint(@erformanceHintManager.Session.Hint int hint)542         public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
543             synchronized (mLock) {
544                 if (mHalSessionPtr == 0 || !updateHintAllowed()) {
545                     return;
546                 }
547                 Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
548                         + " greater than zero.");
549                 mNativeWrapper.halSendHint(mHalSessionPtr, hint);
550             }
551         }
552 
setThreads(@onNull int[] tids)553         public void setThreads(@NonNull int[] tids) {
554             synchronized (mLock) {
555                 if (mHalSessionPtr == 0) {
556                     return;
557                 }
558                 if (tids.length == 0) {
559                     throw new IllegalArgumentException("Thread id list can't be empty.");
560                 }
561                 final int callingUid = Binder.getCallingUid();
562                 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
563                 final long identity = Binder.clearCallingIdentity();
564                 try {
565                     if (!checkTidValid(callingUid, callingTgid, tids)) {
566                         throw new SecurityException("Some tid doesn't belong to the application.");
567                     }
568                 } finally {
569                     Binder.restoreCallingIdentity(identity);
570                 }
571                 if (!updateHintAllowed()) {
572                     Slogf.v(TAG, "update hint not allowed, storing tids.");
573                     mNewThreadIds = tids;
574                     return;
575                 }
576                 mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
577                 mThreadIds = tids;
578             }
579         }
580 
getThreadIds()581         public int[] getThreadIds() {
582             return mThreadIds;
583         }
584 
onProcStateChanged()585         private void onProcStateChanged() {
586             updateHintAllowed();
587         }
588 
pause()589         private void pause() {
590             synchronized (mLock) {
591                 if (mHalSessionPtr == 0) return;
592                 mNativeWrapper.halPauseHintSession(mHalSessionPtr);
593             }
594         }
595 
resume()596         private void resume() {
597             synchronized (mLock) {
598                 if (mHalSessionPtr == 0) return;
599                 mNativeWrapper.halResumeHintSession(mHalSessionPtr);
600                 if (mNewThreadIds != null) {
601                     mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds);
602                     mThreadIds = mNewThreadIds;
603                     mNewThreadIds = null;
604                 }
605             }
606         }
607 
dump(PrintWriter pw, String prefix)608         private void dump(PrintWriter pw, String prefix) {
609             synchronized (mLock) {
610                 pw.println(prefix + "SessionPID: " + mPid);
611                 pw.println(prefix + "SessionUID: " + mUid);
612                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
613                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
614                 pw.println(prefix + "SessionAllowed: " + updateHintAllowed());
615             }
616         }
617 
618         @Override
binderDied()619         public void binderDied() {
620             close();
621         }
622 
623     }
624 }
625