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 android.os.Flags.adpfUseFmqChannel;
20 
21 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
22 import static com.android.server.power.hint.Flags.adpfSessionTag;
23 import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.ActivityManager;
28 import android.app.ActivityManagerInternal;
29 import android.app.StatsManager;
30 import android.app.UidObserver;
31 import android.content.Context;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.hardware.power.ChannelConfig;
35 import android.hardware.power.IPower;
36 import android.hardware.power.SessionConfig;
37 import android.hardware.power.SessionTag;
38 import android.hardware.power.WorkDuration;
39 import android.os.Binder;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.IHintManager;
43 import android.os.IHintSession;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.os.PerformanceHintManager;
47 import android.os.Process;
48 import android.os.RemoteException;
49 import android.os.ServiceManager;
50 import android.os.SystemProperties;
51 import android.text.TextUtils;
52 import android.util.ArrayMap;
53 import android.util.ArraySet;
54 import android.util.IntArray;
55 import android.util.Slog;
56 import android.util.SparseIntArray;
57 import android.util.StatsEvent;
58 
59 import com.android.internal.annotations.GuardedBy;
60 import com.android.internal.annotations.VisibleForTesting;
61 import com.android.internal.util.DumpUtils;
62 import com.android.internal.util.FrameworkStatsLog;
63 import com.android.internal.util.Preconditions;
64 import com.android.server.FgThread;
65 import com.android.server.LocalServices;
66 import com.android.server.ServiceThread;
67 import com.android.server.SystemService;
68 import com.android.server.utils.Slogf;
69 
70 import java.io.FileDescriptor;
71 import java.io.PrintWriter;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.HashMap;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.NoSuchElementException;
78 import java.util.Objects;
79 import java.util.Set;
80 import java.util.TreeMap;
81 import java.util.concurrent.TimeUnit;
82 import java.util.concurrent.atomic.AtomicBoolean;
83 
84 /** An hint service implementation that runs in System Server process. */
85 public final class HintManagerService extends SystemService {
86     private static final String TAG = "HintManagerService";
87     private static final boolean DEBUG = false;
88 
89     private static final int EVENT_CLEAN_UP_UID = 3;
90     @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
91 
92 
93     @VisibleForTesting final long mHintSessionPreferredRate;
94 
95     // Multi-level map storing all active AppHintSessions.
96     // First level is keyed by the UID of the client process creating the session.
97     // Second level is keyed by an IBinder passed from client process. This is used to observe
98     // when the process exits. The client generally uses the same IBinder object across multiple
99     // sessions, so the value is a set of AppHintSessions.
100     @GuardedBy("mLock")
101     private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
102 
103     // Multi-level map storing all the channel binder token death listeners.
104     // First level is keyed by the UID of the client process owning the channel.
105     // Second level is the tgid of the process, which will often just be size one.
106     // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an
107     // object that listens for the death notification of the binder token that was provided by
108     // that client when it created the channel, so we can detect when the client process dies.
109     @GuardedBy("mChannelMapLock")
110     private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
111 
112     /** Lock to protect mActiveSessions and the UidObserver. */
113     private final Object mLock = new Object();
114 
115     /** Lock to protect mChannelMap. */
116     private final Object mChannelMapLock = new Object();
117 
118     @GuardedBy("mNonIsolatedTidsLock")
119     private final Map<Integer, Set<Long>> mNonIsolatedTids;
120 
121     private final Object mNonIsolatedTidsLock = new Object();
122 
123     @VisibleForTesting final MyUidObserver mUidObserver;
124 
125     private final NativeWrapper mNativeWrapper;
126     private final CleanUpHandler mCleanUpHandler;
127 
128     private final ActivityManagerInternal mAmInternal;
129 
130     private final Context mContext;
131 
132     private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true);
133 
134     private final IPower mPowerHal;
135     private int mPowerHalVersion;
136     private final PackageManager mPackageManager;
137 
138     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
139     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
140 
141     @VisibleForTesting final IHintManager.Stub mService = new BinderService();
142 
HintManagerService(Context context)143     public HintManagerService(Context context) {
144         this(context, new Injector());
145     }
146 
147     @VisibleForTesting
HintManagerService(Context context, Injector injector)148     HintManagerService(Context context, Injector injector) {
149         super(context);
150         mContext = context;
151         if (powerhintThreadCleanup()) {
152             mCleanUpHandler = new CleanUpHandler(createCleanUpThread().getLooper());
153             mNonIsolatedTids = new HashMap<>();
154         } else {
155             mCleanUpHandler = null;
156             mNonIsolatedTids = null;
157         }
158         if (adpfSessionTag()) {
159             mPackageManager = mContext.getPackageManager();
160         } else {
161             mPackageManager = null;
162         }
163         mActiveSessions = new ArrayMap<>();
164         mChannelMap = new ArrayMap<>();
165         mNativeWrapper = injector.createNativeWrapper();
166         mNativeWrapper.halInit();
167         mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
168         mUidObserver = new MyUidObserver();
169         mAmInternal = Objects.requireNonNull(
170                 LocalServices.getService(ActivityManagerInternal.class));
171         mPowerHal = injector.createIPower();
172         mPowerHalVersion = 0;
173         if (mPowerHal != null) {
174             try {
175                 mPowerHalVersion = mPowerHal.getInterfaceVersion();
176             } catch (RemoteException e) {
177                 throw new IllegalStateException("Could not contact PowerHAL!", e);
178             }
179         }
180     }
181 
createCleanUpThread()182     private ServiceThread createCleanUpThread() {
183         final ServiceThread handlerThread = new ServiceThread(TAG,
184                 Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/);
185         handlerThread.start();
186         return handlerThread;
187     }
188 
189     @VisibleForTesting
190     static class Injector {
createNativeWrapper()191         NativeWrapper createNativeWrapper() {
192             return new NativeWrapper();
193         }
createIPower()194         IPower createIPower() {
195             return IPower.Stub.asInterface(
196                 ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default"));
197         }
198     }
199 
isHalSupported()200     private boolean isHalSupported() {
201         return mHintSessionPreferredRate != -1;
202     }
203 
204     @Override
onStart()205     public void onStart() {
206         publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService);
207     }
208 
209     @Override
onBootPhase(int phase)210     public void onBootPhase(int phase) {
211         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
212             systemReady();
213         }
214         if (phase == SystemService.PHASE_BOOT_COMPLETED) {
215             registerStatsCallbacks();
216         }
217     }
218 
systemReady()219     private void systemReady() {
220         Slogf.v(TAG, "Initializing HintManager service...");
221         try {
222             ActivityManager.getService().registerUidObserver(mUidObserver,
223                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
224                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
225         } catch (RemoteException e) {
226             // ignored; both services live in system_server
227         }
228 
229     }
230 
registerStatsCallbacks()231     private void registerStatsCallbacks() {
232         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
233         statsManager.setPullAtomCallback(
234                 FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
235                 null, // use default PullAtomMetadata values
236                 DIRECT_EXECUTOR,
237                 this::onPullAtom);
238     }
239 
onPullAtom(int atomTag, @NonNull List<StatsEvent> data)240     private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
241         if (atomTag == FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO) {
242             final boolean isSurfaceFlingerUsingCpuHint =
243                     SystemProperties.getBoolean(PROPERTY_SF_ENABLE_CPU_HINT, false);
244             final boolean isHwuiHintManagerEnabled =
245                     SystemProperties.getBoolean(PROPERTY_HWUI_ENABLE_HINT_MANAGER, false);
246 
247             data.add(FrameworkStatsLog.buildStatsEvent(
248                     FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
249                     isSurfaceFlingerUsingCpuHint,
250                     isHwuiHintManagerEnabled));
251         }
252         return android.app.StatsManager.PULL_SUCCESS;
253     }
254 
255     /**
256      * Wrapper around the static-native methods from native.
257      *
258      * This class exists to allow us to mock static native methods in our tests. If mocking static
259      * methods becomes easier than this in the future, we can delete this class.
260      */
261     @VisibleForTesting
262     public static class NativeWrapper {
nativeInit()263         private native void nativeInit();
264 
nativeGetHintSessionPreferredRate()265         private static native long nativeGetHintSessionPreferredRate();
266 
nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)267         private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
268                 long durationNanos);
269 
nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)270         private static native long nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids,
271                 long durationNanos, int tag, SessionConfig config);
272 
nativePauseHintSession(long halPtr)273         private static native void nativePauseHintSession(long halPtr);
274 
nativeResumeHintSession(long halPtr)275         private static native void nativeResumeHintSession(long halPtr);
276 
nativeCloseHintSession(long halPtr)277         private static native void nativeCloseHintSession(long halPtr);
278 
nativeUpdateTargetWorkDuration( long halPtr, long targetDurationNanos)279         private static native void nativeUpdateTargetWorkDuration(
280                 long halPtr, long targetDurationNanos);
281 
nativeReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)282         private static native void nativeReportActualWorkDuration(
283                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
284 
nativeSendHint(long halPtr, int hint)285         private static native void nativeSendHint(long halPtr, int hint);
286 
nativeSetThreads(long halPtr, int[] tids)287         private static native void nativeSetThreads(long halPtr, int[] tids);
288 
nativeSetMode(long halPtr, int mode, boolean enabled)289         private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
290 
nativeReportActualWorkDuration( long halPtr, WorkDuration[] workDurations)291         private static native void nativeReportActualWorkDuration(
292                 long halPtr, WorkDuration[] workDurations);
293 
294         /** Wrapper for HintManager.nativeInit */
halInit()295         public void halInit() {
296             nativeInit();
297         }
298 
299         /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
halGetHintSessionPreferredRate()300         public long halGetHintSessionPreferredRate() {
301             return nativeGetHintSessionPreferredRate();
302         }
303 
304         /** Wrapper for HintManager.nativeCreateHintSession */
halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)305         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
306             return nativeCreateHintSession(tgid, uid, tids, durationNanos);
307         }
308 
309         /** Wrapper for HintManager.nativeCreateHintSessionWithConfig */
halCreateHintSessionWithConfig( int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)310         public long halCreateHintSessionWithConfig(
311                 int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config) {
312             return nativeCreateHintSessionWithConfig(tgid, uid, tids, durationNanos, tag, config);
313         }
314 
315         /** Wrapper for HintManager.nativePauseHintSession */
halPauseHintSession(long halPtr)316         public void halPauseHintSession(long halPtr) {
317             nativePauseHintSession(halPtr);
318         }
319 
320         /** Wrapper for HintManager.nativeResumeHintSession */
halResumeHintSession(long halPtr)321         public void halResumeHintSession(long halPtr) {
322             nativeResumeHintSession(halPtr);
323         }
324 
325         /** Wrapper for HintManager.nativeCloseHintSession */
halCloseHintSession(long halPtr)326         public void halCloseHintSession(long halPtr) {
327             nativeCloseHintSession(halPtr);
328         }
329 
330         /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */
halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos)331         public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
332             nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos);
333         }
334 
335         /** Wrapper for HintManager.nativeReportActualWorkDuration */
halReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)336         public void halReportActualWorkDuration(
337                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
338             nativeReportActualWorkDuration(halPtr, actualDurationNanos,
339                     timeStampNanos);
340         }
341 
342         /** Wrapper for HintManager.sendHint */
halSendHint(long halPtr, int hint)343         public void halSendHint(long halPtr, int hint) {
344             nativeSendHint(halPtr, hint);
345         }
346 
347         /** Wrapper for HintManager.nativeSetThreads */
halSetThreads(long halPtr, int[] tids)348         public void halSetThreads(long halPtr, int[] tids) {
349             nativeSetThreads(halPtr, tids);
350         }
351 
352         /** Wrapper for HintManager.setMode */
halSetMode(long halPtr, int mode, boolean enabled)353         public void halSetMode(long halPtr, int mode, boolean enabled) {
354             nativeSetMode(halPtr, mode, enabled);
355         }
356 
357         /** Wrapper for HintManager.nativeReportActualWorkDuration */
halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations)358         public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
359             nativeReportActualWorkDuration(halPtr, workDurations);
360         }
361     }
362 
363     @VisibleForTesting
364     final class MyUidObserver extends UidObserver {
365         @GuardedBy("mLock")
366         private final SparseIntArray mProcStatesCache = new SparseIntArray();
isUidForeground(int uid)367         public boolean isUidForeground(int uid) {
368             synchronized (mLock) {
369                 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
370                         <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
371             }
372         }
373 
374         @Override
onUidGone(int uid, boolean disabled)375         public void onUidGone(int uid, boolean disabled) {
376             FgThread.getHandler().post(() -> {
377                 synchronized (mLock) {
378                     mProcStatesCache.delete(uid);
379                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
380                     if (tokenMap == null) {
381                         return;
382                     }
383                     Slog.d(TAG, "Uid gone for " + uid);
384                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
385                         // Will remove the session from tokenMap
386                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
387                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
388                             sessionSet.valueAt(j).close();
389                         }
390                     }
391                 }
392                 synchronized (mChannelMapLock) {
393                     // Clean up the uid's session channels
394                     final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
395                     if (uidMap != null) {
396                         for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) {
397                             entry.getValue().closeChannel();
398                         }
399                         mChannelMap.remove(uid);
400                     }
401                 }
402             });
403         }
404 
405         /**
406          * The IUidObserver callback is called from the system_server, so it'll be a direct function
407          * call from ActivityManagerService. Do not do heavy logic here.
408          */
409         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)410         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
411             FgThread.getHandler().post(() -> {
412                 synchronized (mLock) {
413                     boolean shouldCleanup = false;
414                     if (mPowerHalVersion >= 4 && powerhintThreadCleanup()) {
415                         int prevProcState = mProcStatesCache.get(uid, Integer.MAX_VALUE);
416                         shouldCleanup =
417                                 prevProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
418                                         && procState
419                                         > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
420                     }
421 
422                     mProcStatesCache.put(uid, procState);
423                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
424                     if (tokenMap == null) {
425                         return;
426                     }
427                     if (shouldCleanup && powerhintThreadCleanup()) {
428                         final Message msg = mCleanUpHandler.obtainMessage(EVENT_CLEAN_UP_UID,
429                                 uid);
430                         mCleanUpHandler.sendMessageDelayed(msg, CLEAN_UP_UID_DELAY_MILLIS);
431                         Slog.d(TAG, "Sent cleanup message for uid " + uid);
432                     }
433                     boolean shouldAllowUpdate = isUidForeground(uid);
434                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
435                         final ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
436                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
437                             sessionSet.valueAt(j).updateHintAllowedByProcState(shouldAllowUpdate);
438                         }
439                     }
440                 }
441             });
442         }
443     }
444 
445     /**
446      * Creates a channel item in the channel map if one does not exist, then returns
447      * the entry in the channel map.
448      */
getOrCreateMappedChannelItem(int tgid, int uid, IBinder token)449     public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) {
450         synchronized (mChannelMapLock) {
451             if (!mChannelMap.containsKey(uid)) {
452                 mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>());
453             }
454             TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
455             if (!map.containsKey(tgid)) {
456                 ChannelItem item = new ChannelItem(tgid, uid, token);
457                 item.openChannel();
458                 map.put(tgid, item);
459             }
460             return map.get(tgid);
461         }
462     }
463 
464     /**
465      * This removes an entry in the binder token callback map when a channel is closed,
466      * and unregisters its callbacks.
467      */
removeChannelItem(Integer tgid, Integer uid)468     public void removeChannelItem(Integer tgid, Integer uid) {
469         synchronized (mChannelMapLock) {
470             TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
471             if (map != null) {
472                 ChannelItem item = map.get(tgid);
473                 if (item != null) {
474                     item.closeChannel();
475                     map.remove(tgid);
476                 }
477                 if (map.isEmpty()) {
478                     mChannelMap.remove(uid);
479                 }
480             }
481         }
482     }
483 
484     /**
485      * Manages the lifecycle of a single channel. This includes caching the channel descriptor,
486      * receiving binder token death notifications, and handling cleanup on uid termination. There
487      * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens
488      * when a ChannelItem enters the map, while destruction happens when it leaves the map.
489      */
490     private class ChannelItem implements IBinder.DeathRecipient {
491         @Override
binderDied()492         public void binderDied() {
493             removeChannelItem(mTgid, mUid);
494         }
495 
ChannelItem(int tgid, int uid, IBinder token)496         ChannelItem(int tgid, int uid, IBinder token) {
497             this.mTgid = tgid;
498             this.mUid = uid;
499             this.mToken = token;
500             this.mLinked = false;
501             this.mConfig = null;
502         }
503 
closeChannel()504         public void closeChannel() {
505             if (mLinked) {
506                 mToken.unlinkToDeath(this, 0);
507                 mLinked = false;
508             }
509             if (mConfig != null) {
510                 try  {
511                     mPowerHal.closeSessionChannel(mTgid, mUid);
512                 } catch (RemoteException e) {
513                     throw new IllegalStateException("Failed to close session channel!", e);
514                 }
515                 mConfig = null;
516             }
517         }
518 
openChannel()519         public void openChannel() {
520             if (!mLinked) {
521                 try {
522                     mToken.linkToDeath(this, 0);
523                 } catch (RemoteException e) {
524                     throw new IllegalStateException("Client already dead", e);
525                 }
526                 mLinked = true;
527             }
528             if (mConfig == null) {
529                 try {
530                     // This method uses PowerHAL directly through the SDK,
531                     // to avoid needing to pass the ChannelConfig through JNI.
532                     mConfig = mPowerHal.getSessionChannel(mTgid, mUid);
533                 } catch (RemoteException e) {
534                     removeChannelItem(mTgid, mUid);
535                     throw new IllegalStateException("Failed to create session channel!", e);
536                 }
537             }
538         }
539 
getConfig()540         ChannelConfig getConfig() {
541             return mConfig;
542         }
543 
544         // To avoid accidental double-linking / unlinking
545         boolean mLinked;
546         final int mTgid;
547         final int mUid;
548         final IBinder mToken;
549         ChannelConfig mConfig;
550     }
551 
552     final class CleanUpHandler extends Handler {
553         // status of processed tid used for caching
554         private static final int TID_NOT_CHECKED = 0;
555         private static final int TID_PASSED_CHECK = 1;
556         private static final int TID_EXITED = 2;
557 
CleanUpHandler(Looper looper)558         CleanUpHandler(Looper looper) {
559             super(looper);
560         }
561 
562         @Override
handleMessage(Message msg)563         public void handleMessage(Message msg) {
564             if (msg.what == EVENT_CLEAN_UP_UID) {
565                 if (hasEqualMessages(msg.what, msg.obj)) {
566                     removeEqualMessages(msg.what, msg.obj);
567                     final Message newMsg = obtainMessage(msg.what, msg.obj);
568                     sendMessageDelayed(newMsg, CLEAN_UP_UID_DELAY_MILLIS);
569                     Slog.d(TAG, "Duplicate messages for " + msg.obj);
570                     return;
571                 }
572                 Slog.d(TAG, "Starts cleaning for " + msg.obj);
573                 final int uid = (int) msg.obj;
574                 boolean isForeground = mUidObserver.isUidForeground(uid);
575                 // store all sessions in a list and release the global lock
576                 // we don't need to worry about stale data or racing as the session is synchronized
577                 // itself and will perform its own closed status check in setThreads call
578                 final List<AppHintSession> sessions;
579                 synchronized (mLock) {
580                     final ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
581                             mActiveSessions.get(uid);
582                     if (tokenMap == null || tokenMap.isEmpty()) {
583                         return;
584                     }
585                     sessions = new ArrayList<>(tokenMap.size());
586                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
587                         final ArraySet<AppHintSession> set = tokenMap.valueAt(i);
588                         for (int j = set.size() - 1; j >= 0; j--) {
589                             sessions.add(set.valueAt(j));
590                         }
591                     }
592                 }
593                 final long[] durationList = new long[sessions.size()];
594                 final int[] invalidTidCntList = new int[sessions.size()];
595                 final SparseIntArray checkedTids = new SparseIntArray();
596                 int[] totalTidCnt = new int[1];
597                 for (int i = sessions.size() - 1; i >= 0; i--) {
598                     final AppHintSession session = sessions.get(i);
599                     final long start = System.nanoTime();
600                     try {
601                         final int invalidCnt = cleanUpSession(session, checkedTids, totalTidCnt);
602                         final long elapsed = System.nanoTime() - start;
603                         invalidTidCntList[i] = invalidCnt;
604                         durationList[i] = elapsed;
605                     } catch (Exception e) {
606                         Slog.e(TAG, "Failed to clean up session " + session.mHalSessionPtr
607                                 + " for UID " + session.mUid);
608                     }
609                 }
610                 logCleanUpMetrics(uid, invalidTidCntList, durationList, sessions.size(),
611                         totalTidCnt[0], isForeground);
612             }
613         }
614 
logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt, int totalTidCnt, boolean isForeground)615         private void logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt,
616                 int totalTidCnt, boolean isForeground) {
617             int maxInvalidTidCnt = Integer.MIN_VALUE;
618             int totalInvalidTidCnt = 0;
619             for (int i = 0; i < count.length; i++) {
620                 totalInvalidTidCnt += count[i];
621                 maxInvalidTidCnt = Math.max(maxInvalidTidCnt, count[i]);
622             }
623             if (DEBUG || totalInvalidTidCnt > 0) {
624                 Arrays.sort(durationNsList);
625                 long totalDurationNs = 0;
626                 for (int i = 0; i < durationNsList.length; i++) {
627                     totalDurationNs += durationNsList[i];
628                 }
629                 int totalDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(totalDurationNs);
630                 int maxDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
631                         durationNsList[durationNsList.length - 1]);
632                 int minDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(durationNsList[0]);
633                 int avgDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
634                         totalDurationNs / durationNsList.length);
635                 int th90DurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
636                         durationNsList[(int) (durationNsList.length * 0.9)]);
637                 FrameworkStatsLog.write(FrameworkStatsLog.ADPF_HINT_SESSION_TID_CLEANUP, uid,
638                         totalDurationUs, maxDurationUs, totalTidCnt, totalInvalidTidCnt,
639                         maxInvalidTidCnt, sessionCnt, isForeground);
640                 Slog.w(TAG,
641                         "Invalid tid found for UID" + uid + " in " + totalDurationUs + "us:\n\t"
642                                 + "count("
643                                 + " session: " + sessionCnt
644                                 + " totalTid: " + totalTidCnt
645                                 + " maxInvalidTid: " + maxInvalidTidCnt
646                                 + " totalInvalidTid: " + totalInvalidTidCnt + ")\n\t"
647                                 + "time per session("
648                                 + " min: " + minDurationUs + "us"
649                                 + " max: " + maxDurationUs + "us"
650                                 + " avg: " + avgDurationUs + "us"
651                                 + " 90%: " + th90DurationUs + "us" + ")\n\t"
652                                 + "isForeground: " + isForeground);
653             }
654         }
655 
656         // This will check if each TID currently linked to the session still exists. If it's
657         // previously registered as not an isolated process, then it will run tkill(pid, tid, 0) to
658         // verify that it's still running under the same pid. Otherwise, it will run
659         // kill(tid, 0) to only check if it exists. The result will be cached in checkedTids
660         // map with tid as the key and checked status as value.
cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total)661         public int cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total) {
662             if (session.isClosed() || session.isForcePaused()) {
663                 return 0;
664             }
665             final int pid = session.mPid;
666             final int[] tids = session.getTidsInternal();
667             if (total != null && total.length == 1) {
668                 total[0] += tids.length;
669             }
670             final IntArray filtered = new IntArray(tids.length);
671             for (int i = 0; i < tids.length; i++) {
672                 int tid = tids[i];
673                 if (checkedTids.get(tid, 0) != TID_NOT_CHECKED) {
674                     if (checkedTids.get(tid) == TID_PASSED_CHECK) {
675                         filtered.add(tid);
676                     }
677                     continue;
678                 }
679                 // if it was registered as a non-isolated then we perform more restricted check
680                 final boolean isNotIsolated;
681                 synchronized (mNonIsolatedTidsLock) {
682                     isNotIsolated = mNonIsolatedTids.containsKey(tid);
683                 }
684                 try {
685                     if (isNotIsolated) {
686                         Process.checkTid(pid, tid);
687                     } else {
688                         Process.checkPid(tid);
689                     }
690                     checkedTids.put(tid, TID_PASSED_CHECK);
691                     filtered.add(tid);
692                 } catch (NoSuchElementException e) {
693                     checkedTids.put(tid, TID_EXITED);
694                 } catch (Exception e) {
695                     Slog.w(TAG, "Unexpected exception when checking TID " + tid + " under PID "
696                             + pid + "(isolated: " + !isNotIsolated + ")", e);
697                     // if anything unexpected happens then we keep it, but don't store it as checked
698                     filtered.add(tid);
699                 }
700             }
701             final int diff = tids.length - filtered.size();
702             if (diff > 0) {
703                 synchronized (session) {
704                     // in case thread list is updated during the cleanup then we skip updating
705                     // the session but just return the number for reporting purpose
706                     final int[] newTids = session.getTidsInternal();
707                     if (newTids.length != tids.length) {
708                         Slog.d(TAG, "Skipped cleaning up the session as new tids are added");
709                         return diff;
710                     }
711                     Arrays.sort(newTids);
712                     Arrays.sort(tids);
713                     if (!Arrays.equals(newTids, tids)) {
714                         Slog.d(TAG, "Skipped cleaning up the session as new tids are updated");
715                         return diff;
716                     }
717                     Slog.d(TAG, "Cleaned up " + diff + " invalid tids for session "
718                             + session.mHalSessionPtr + " with UID " + session.mUid + "\n\t"
719                             + "before: " + Arrays.toString(tids) + "\n\t"
720                             + "after: " + filtered);
721                     final int[] filteredTids = filtered.toArray();
722                     if (filteredTids.length == 0) {
723                         session.mShouldForcePause = true;
724                         if (session.mUpdateAllowedByProcState) {
725                             session.pause();
726                         }
727                     } else {
728                         session.setThreadsInternal(filteredTids, false);
729                     }
730                 }
731             }
732             return diff;
733         }
734     }
735 
736     @VisibleForTesting
getBinderServiceInstance()737     IHintManager.Stub getBinderServiceInstance() {
738         return mService;
739     }
740 
741     @VisibleForTesting
hasChannel(int tgid, int uid)742     Boolean hasChannel(int tgid, int uid) {
743         synchronized (mChannelMapLock) {
744             TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
745             if (uidMap != null) {
746                 ChannelItem item = uidMap.get(tgid);
747                 return item != null;
748             }
749             return false;
750         }
751     }
752 
753     // returns the first invalid tid or null if not found
checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated)754     private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
755         // Make sure all tids belongs to the same UID (including isolated UID),
756         // tids can belong to different application processes.
757         List<Integer> isolatedPids = null;
758         for (int i = 0; i < tids.length; i++) {
759             int tid = tids[i];
760             final String[] procStatusKeys = new String[] {
761                     "Uid:",
762                     "Tgid:"
763             };
764             long[] output = new long[procStatusKeys.length];
765             Process.readProcLines("/proc/" + tid + "/status", procStatusKeys, output);
766             int uidOfThreadId = (int) output[0];
767             int pidOfThreadId = (int) output[1];
768 
769             // use PID check for non-isolated processes
770             if (nonIsolated != null && pidOfThreadId == tgid) {
771                 nonIsolated.add(tid);
772                 continue;
773             }
774             // use UID check for isolated processes.
775             if (uidOfThreadId == uid) {
776                 continue;
777             }
778             // Only call into AM if the tid is either isolated or invalid
779             if (isolatedPids == null) {
780                 // To avoid deadlock, do not call into AMS if the call is from system.
781                 if (uid == Process.SYSTEM_UID) {
782                     return tid;
783                 }
784                 isolatedPids = mAmInternal.getIsolatedProcesses(uid);
785                 if (isolatedPids == null) {
786                     return tid;
787                 }
788             }
789             if (isolatedPids.contains(pidOfThreadId)) {
790                 continue;
791             }
792             return tid;
793         }
794         return null;
795     }
796 
formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid)797     private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) {
798         return "Tid" + invalidTid + " from list " + Arrays.toString(tids)
799                 + " doesn't belong to the calling application " + callingUid;
800     }
801 
802     @VisibleForTesting
803     final class BinderService extends IHintManager.Stub {
804         @Override
createHintSessionWithConfig(@onNull IBinder token, @NonNull int[] tids, long durationNanos, @SessionTag int tag, @Nullable SessionConfig config)805         public IHintSession createHintSessionWithConfig(@NonNull IBinder token,
806                 @NonNull int[] tids, long durationNanos, @SessionTag int tag,
807                 @Nullable SessionConfig config) {
808             if (!isHalSupported()) {
809                 throw new UnsupportedOperationException("PowerHAL is not supported!");
810             }
811 
812             java.util.Objects.requireNonNull(token);
813             java.util.Objects.requireNonNull(tids);
814             Preconditions.checkArgument(tids.length != 0, "tids should"
815                     + " not be empty.");
816 
817             final int callingUid = Binder.getCallingUid();
818             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
819             final long identity = Binder.clearCallingIdentity();
820             try {
821                 final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length)
822                         : null;
823                 final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids,
824                         nonIsolated);
825                 if (invalidTid != null) {
826                     final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid);
827                     Slogf.w(TAG, errMsg);
828                     throw new SecurityException(errMsg);
829                 }
830 
831                 if (adpfSessionTag() && tag == SessionTag.APP) {
832                     // If the category of the app is a game,
833                     // we change the session tag to SessionTag.GAME
834                     // as it was not previously classified
835                     switch (getUidApplicationCategory(callingUid)) {
836                         case ApplicationInfo.CATEGORY_GAME:
837                             tag = SessionTag.GAME;
838                             break;
839                         case ApplicationInfo.CATEGORY_UNDEFINED:
840                             // We use CATEGORY_UNDEFINED to filter the case when
841                             // PackageManager.NameNotFoundException is caught,
842                             // which should not happen.
843                             tag = SessionTag.APP;
844                             break;
845                         default:
846                             tag = SessionTag.APP;
847                     }
848                 }
849 
850                 Long halSessionPtr = null;
851                 if (mConfigCreationSupport.get()) {
852                     try {
853                         halSessionPtr = mNativeWrapper.halCreateHintSessionWithConfig(
854                                 callingTgid, callingUid, tids, durationNanos, tag, config);
855                     } catch (UnsupportedOperationException e) {
856                         mConfigCreationSupport.set(false);
857                     } catch (IllegalStateException e) {
858                         Slog.e("createHintSessionWithConfig failed: ", e.getMessage());
859                         throw new IllegalStateException(
860                             "createHintSessionWithConfig failed: " + e.getMessage());
861                     }
862                 }
863 
864                 if (halSessionPtr == null) {
865                     try {
866                         halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid,
867                                 callingUid, tids, durationNanos);
868                     } catch (UnsupportedOperationException e) {
869                         Slog.w("createHintSession unsupported: ", e.getMessage());
870                         throw new UnsupportedOperationException(
871                             "createHintSession unsupported: " + e.getMessage());
872                     } catch (IllegalStateException e) {
873                         Slog.e("createHintSession failed: ", e.getMessage());
874                         throw new IllegalStateException(
875                             "createHintSession failed: " + e.getMessage());
876                     }
877                 }
878 
879                 if (powerhintThreadCleanup()) {
880                     synchronized (mNonIsolatedTidsLock) {
881                         for (int i = nonIsolated.size() - 1; i >= 0; i--) {
882                             mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), new ArraySet<>());
883                             mNonIsolatedTids.get(nonIsolated.get(i)).add(halSessionPtr);
884                         }
885                     }
886                 }
887 
888                 final long sessionId = config != null ? config.id : halSessionPtr;
889                 logPerformanceHintSessionAtom(
890                         callingUid, sessionId, durationNanos, tids, tag);
891 
892                 synchronized (mLock) {
893                     AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
894                             halSessionPtr, durationNanos);
895                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
896                             mActiveSessions.get(callingUid);
897                     if (tokenMap == null) {
898                         tokenMap = new ArrayMap<>(1);
899                         mActiveSessions.put(callingUid, tokenMap);
900                     }
901                     ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
902                     if (sessionSet == null) {
903                         sessionSet = new ArraySet<>(1);
904                         tokenMap.put(token, sessionSet);
905                     }
906                     sessionSet.add(hs);
907                     return hs;
908                 }
909             } finally {
910                 Binder.restoreCallingIdentity(identity);
911             }
912         }
913 
914         @Override
getSessionChannel(IBinder token)915         public ChannelConfig getSessionChannel(IBinder token) {
916             if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
917                 return null;
918             }
919             java.util.Objects.requireNonNull(token);
920             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
921             final int callingUid = Binder.getCallingUid();
922             ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token);
923             return item.getConfig();
924         };
925 
926         @Override
closeSessionChannel()927         public void closeSessionChannel() {
928             if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
929                 return;
930             }
931             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
932             final int callingUid = Binder.getCallingUid();
933             removeChannelItem(callingTgid, callingUid);
934         };
935 
936         @Override
getHintSessionPreferredRate()937         public long getHintSessionPreferredRate() {
938             return mHintSessionPreferredRate;
939         }
940 
941         @Override
setHintSessionThreads(@onNull IHintSession hintSession, @NonNull int[] tids)942         public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) {
943             AppHintSession appHintSession = (AppHintSession) hintSession;
944             appHintSession.setThreads(tids);
945         }
946 
947         @Override
getHintSessionThreadIds(@onNull IHintSession hintSession)948         public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) {
949             AppHintSession appHintSession = (AppHintSession) hintSession;
950             return appHintSession.getThreadIds();
951         }
952 
953         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)954         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
955             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
956                 return;
957             }
958             pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
959             pw.println("HAL Support: " + isHalSupported());
960             pw.println("Active Sessions:");
961             synchronized (mLock) {
962                 for (int i = 0; i < mActiveSessions.size(); i++) {
963                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
964                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
965                             mActiveSessions.valueAt(i);
966                     for (int j = 0; j < tokenMap.size(); j++) {
967                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
968                         for (int k = 0; k < sessionSet.size(); ++k) {
969                             pw.println("  Session:");
970                             sessionSet.valueAt(k).dump(pw, "    ");
971                         }
972                     }
973                 }
974             }
975         }
976 
logPerformanceHintSessionAtom(int uid, long sessionId, long targetDuration, int[] tids, @SessionTag int sessionTag)977         private void logPerformanceHintSessionAtom(int uid, long sessionId,
978                 long targetDuration, int[] tids, @SessionTag int sessionTag) {
979             FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid,
980                     sessionId, targetDuration, tids.length, sessionTag);
981         }
982 
getUidApplicationCategory(int uid)983         private int getUidApplicationCategory(int uid) {
984             try {
985                 final String packageName = mPackageManager.getNameForUid(uid);
986                 final ApplicationInfo applicationInfo =
987                         mPackageManager.getApplicationInfo(packageName, PackageManager.MATCH_ALL);
988                 return applicationInfo.category;
989             } catch (PackageManager.NameNotFoundException e) {
990                 return ApplicationInfo.CATEGORY_UNDEFINED;
991             }
992         }
993     }
994 
995     @VisibleForTesting
996     final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
997         protected final int mUid;
998         protected final int mPid;
999         protected int[] mThreadIds;
1000         protected final IBinder mToken;
1001         protected long mHalSessionPtr;
1002         protected long mTargetDurationNanos;
1003         protected boolean mUpdateAllowedByProcState;
1004         protected int[] mNewThreadIds;
1005         protected boolean mPowerEfficient;
1006         protected boolean mShouldForcePause;
1007 
1008         private enum SessionModes {
1009             POWER_EFFICIENCY,
1010         };
1011 
AppHintSession( int uid, int pid, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos)1012         protected AppHintSession(
1013                 int uid, int pid, int[] threadIds, IBinder token,
1014                 long halSessionPtr, long durationNanos) {
1015             mUid = uid;
1016             mPid = pid;
1017             mToken = token;
1018             mThreadIds = threadIds;
1019             mHalSessionPtr = halSessionPtr;
1020             mTargetDurationNanos = durationNanos;
1021             mUpdateAllowedByProcState = true;
1022             mPowerEfficient = false;
1023             mShouldForcePause = false;
1024             final boolean allowed = mUidObserver.isUidForeground(mUid);
1025             updateHintAllowedByProcState(allowed);
1026             try {
1027                 token.linkToDeath(this, 0);
1028             } catch (RemoteException e) {
1029                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
1030                 throw new IllegalStateException("Client already dead", e);
1031             }
1032         }
1033 
1034         @VisibleForTesting
updateHintAllowedByProcState(boolean allowed)1035         boolean updateHintAllowedByProcState(boolean allowed) {
1036             synchronized (this) {
1037                 if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) resume();
1038                 if (!allowed && mUpdateAllowedByProcState) pause();
1039                 mUpdateAllowedByProcState = allowed;
1040                 return mUpdateAllowedByProcState;
1041             }
1042         }
1043 
isHintAllowed()1044         boolean isHintAllowed() {
1045             return mHalSessionPtr != 0 && mUpdateAllowedByProcState && !mShouldForcePause;
1046         }
1047 
1048         @Override
updateTargetWorkDuration(long targetDurationNanos)1049         public void updateTargetWorkDuration(long targetDurationNanos) {
1050             synchronized (this) {
1051                 if (!isHintAllowed()) {
1052                     return;
1053                 }
1054                 Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
1055                         + " the target duration to be greater than 0.");
1056                 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
1057                 mTargetDurationNanos = targetDurationNanos;
1058             }
1059         }
1060 
1061         @Override
reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos)1062         public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
1063             synchronized (this) {
1064                 if (!isHintAllowed()) {
1065                     return;
1066                 }
1067                 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
1068                         + " of hint durations shouldn't be 0.");
1069                 Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length,
1070                         "The length of durations and timestamps should be the same.");
1071                 for (int i = 0; i < actualDurationNanos.length; i++) {
1072                     if (actualDurationNanos[i] <= 0) {
1073                         throw new IllegalArgumentException(
1074                                 String.format("durations[%d]=%d should be greater than 0",
1075                                         i, actualDurationNanos[i]));
1076                     }
1077                 }
1078                 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos,
1079                         timeStampNanos);
1080             }
1081         }
1082 
1083         /** TODO: consider monitor session threads and close session if any thread is dead. */
1084         @Override
close()1085         public void close() {
1086             synchronized (this) {
1087                 if (mHalSessionPtr == 0) return;
1088                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
1089                 mHalSessionPtr = 0;
1090                 try {
1091                     mToken.unlinkToDeath(this, 0);
1092                 } catch (NoSuchElementException ignored) {
1093                     Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
1094                 }
1095             }
1096             synchronized (mLock) {
1097                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
1098                 if (tokenMap == null) {
1099                     Slogf.w(TAG, "UID %d is not present in active session map", mUid);
1100                     return;
1101                 }
1102                 ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
1103                 if (sessionSet == null) {
1104                     Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
1105                     return;
1106                 }
1107                 sessionSet.remove(this);
1108                 if (sessionSet.isEmpty()) tokenMap.remove(mToken);
1109                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
1110             }
1111             if (powerhintThreadCleanup()) {
1112                 synchronized (mNonIsolatedTidsLock) {
1113                     final int[] tids = getTidsInternal();
1114                     for (int tid : tids) {
1115                         if (mNonIsolatedTids.containsKey(tid)) {
1116                             mNonIsolatedTids.get(tid).remove(mHalSessionPtr);
1117                             if (mNonIsolatedTids.get(tid).isEmpty()) {
1118                                 mNonIsolatedTids.remove(tid);
1119                             }
1120                         }
1121                     }
1122                 }
1123             }
1124         }
1125 
1126         @Override
sendHint(@erformanceHintManager.Session.Hint int hint)1127         public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
1128             synchronized (this) {
1129                 if (!isHintAllowed()) {
1130                     return;
1131                 }
1132                 Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
1133                         + " greater than zero.");
1134                 mNativeWrapper.halSendHint(mHalSessionPtr, hint);
1135             }
1136         }
1137 
setThreads(@onNull int[] tids)1138         public void setThreads(@NonNull int[] tids) {
1139             setThreadsInternal(tids, true);
1140         }
1141 
setThreadsInternal(int[] tids, boolean checkTid)1142         private void setThreadsInternal(int[] tids, boolean checkTid) {
1143             if (tids.length == 0) {
1144                 throw new IllegalArgumentException("Thread id list can't be empty.");
1145             }
1146 
1147             synchronized (this) {
1148                 if (mHalSessionPtr == 0) {
1149                     return;
1150                 }
1151                 if (!mUpdateAllowedByProcState) {
1152                     Slogf.v(TAG, "update hint not allowed, storing tids.");
1153                     mNewThreadIds = tids;
1154                     mShouldForcePause = false;
1155                     return;
1156                 }
1157                 if (checkTid) {
1158                     final int callingUid = Binder.getCallingUid();
1159                     final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
1160                     final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null;
1161                     final long identity = Binder.clearCallingIdentity();
1162                     try {
1163                         final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids,
1164                                 nonIsolated);
1165                         if (invalidTid != null) {
1166                             final String errMsg = formatTidCheckErrMsg(callingUid, tids,
1167                                     invalidTid);
1168                             Slogf.w(TAG, errMsg);
1169                             throw new SecurityException(errMsg);
1170                         }
1171                         if (powerhintThreadCleanup()) {
1172                             synchronized (mNonIsolatedTidsLock) {
1173                                 for (int i = nonIsolated.size() - 1; i >= 0; i--) {
1174                                     mNonIsolatedTids.putIfAbsent(nonIsolated.get(i),
1175                                             new ArraySet<>());
1176                                     mNonIsolatedTids.get(nonIsolated.get(i)).add(mHalSessionPtr);
1177                                 }
1178                             }
1179                         }
1180                     } finally {
1181                         Binder.restoreCallingIdentity(identity);
1182                     }
1183                 }
1184                 mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
1185                 mThreadIds = tids;
1186                 mNewThreadIds = null;
1187                 // if the update is allowed but the session is force paused by tid clean up, then
1188                 // it's waiting for this tid update to resume
1189                 if (mShouldForcePause) {
1190                     resume();
1191                     mShouldForcePause = false;
1192                 }
1193             }
1194         }
1195 
getThreadIds()1196         public int[] getThreadIds() {
1197             synchronized (this) {
1198                 return Arrays.copyOf(mThreadIds, mThreadIds.length);
1199             }
1200         }
1201 
1202         @VisibleForTesting
getTidsInternal()1203         int[] getTidsInternal() {
1204             synchronized (this) {
1205                 return mNewThreadIds != null ? Arrays.copyOf(mNewThreadIds, mNewThreadIds.length)
1206                         : Arrays.copyOf(mThreadIds, mThreadIds.length);
1207             }
1208         }
1209 
isClosed()1210         boolean isClosed() {
1211             synchronized (this) {
1212                 return mHalSessionPtr == 0;
1213             }
1214         }
1215 
isForcePaused()1216         boolean isForcePaused() {
1217             synchronized (this) {
1218                 return mShouldForcePause;
1219             }
1220         }
1221         @Override
setMode(int mode, boolean enabled)1222         public void setMode(int mode, boolean enabled) {
1223             synchronized (this) {
1224                 if (!isHintAllowed()) {
1225                     return;
1226                 }
1227                 Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
1228                         + " greater than zero.");
1229                 if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
1230                     mPowerEfficient = enabled;
1231                 }
1232                 mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
1233             }
1234         }
1235 
1236         @Override
reportActualWorkDuration2(WorkDuration[] workDurations)1237         public void reportActualWorkDuration2(WorkDuration[] workDurations) {
1238             synchronized (this) {
1239                 if (!isHintAllowed()) {
1240                     return;
1241                 }
1242                 Preconditions.checkArgument(workDurations.length != 0, "the count"
1243                         + " of work durations shouldn't be 0.");
1244                 for (int i = 0; i < workDurations.length; i++) {
1245                     validateWorkDuration(workDurations[i]);
1246                 }
1247                 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
1248             }
1249         }
1250 
isPowerEfficient()1251         public boolean isPowerEfficient() {
1252             synchronized (this) {
1253                 return mPowerEfficient;
1254             }
1255         }
1256 
validateWorkDuration(WorkDuration workDuration)1257         void validateWorkDuration(WorkDuration workDuration) {
1258             if (DEBUG) {
1259                 Slogf.d(TAG, "WorkDuration("
1260                         + workDuration.durationNanos + ", "
1261                         + workDuration.workPeriodStartTimestampNanos + ", "
1262                         + workDuration.cpuDurationNanos + ", "
1263                         + workDuration.gpuDurationNanos + ")");
1264             }
1265 
1266             // Allow work period start timestamp to be zero in system server side because
1267             // legacy API call will use zero value. It can not be estimated with the timestamp
1268             // the sample is received because the samples could stack up.
1269             if (workDuration.durationNanos <= 0) {
1270                 throw new IllegalArgumentException(
1271                     TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
1272                             workDuration.durationNanos));
1273             }
1274             if (workDuration.workPeriodStartTimestampNanos < 0) {
1275                 throw new IllegalArgumentException(
1276                     TextUtils.formatSimple(
1277                             "Work period start timestamp (%d) should be greater than 0",
1278                             workDuration.workPeriodStartTimestampNanos));
1279             }
1280             if (workDuration.cpuDurationNanos < 0) {
1281                 throw new IllegalArgumentException(
1282                     TextUtils.formatSimple(
1283                         "Actual CPU duration (%d) should be greater than or equal to 0",
1284                             workDuration.cpuDurationNanos));
1285             }
1286             if (workDuration.gpuDurationNanos < 0) {
1287                 throw new IllegalArgumentException(
1288                     TextUtils.formatSimple(
1289                         "Actual GPU duration (%d) should greater than or equal to 0",
1290                             workDuration.gpuDurationNanos));
1291             }
1292             if (workDuration.cpuDurationNanos
1293                     + workDuration.gpuDurationNanos <= 0) {
1294                 throw new IllegalArgumentException(
1295                     TextUtils.formatSimple(
1296                         "The actual CPU duration (%d) and the actual GPU duration (%d)"
1297                         + " should not both be 0", workDuration.cpuDurationNanos,
1298                         workDuration.gpuDurationNanos));
1299             }
1300         }
1301 
pause()1302         private void pause() {
1303             synchronized (this) {
1304                 if (mHalSessionPtr == 0) return;
1305                 mNativeWrapper.halPauseHintSession(mHalSessionPtr);
1306             }
1307         }
1308 
resume()1309         private void resume() {
1310             synchronized (this) {
1311                 if (mHalSessionPtr == 0) return;
1312                 mNativeWrapper.halResumeHintSession(mHalSessionPtr);
1313                 if (mNewThreadIds != null) {
1314                     mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds);
1315                     mThreadIds = mNewThreadIds;
1316                     mNewThreadIds = null;
1317                 }
1318             }
1319         }
1320 
dump(PrintWriter pw, String prefix)1321         private void dump(PrintWriter pw, String prefix) {
1322             synchronized (this) {
1323                 pw.println(prefix + "SessionPID: " + mPid);
1324                 pw.println(prefix + "SessionUID: " + mUid);
1325                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
1326                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
1327                 pw.println(prefix + "SessionAllowedByProcState: " + mUpdateAllowedByProcState);
1328                 pw.println(prefix + "SessionForcePaused: " + mShouldForcePause);
1329                 pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
1330             }
1331         }
1332 
1333         @Override
binderDied()1334         public void binderDied() {
1335             close();
1336         }
1337 
1338     }
1339 }
1340