• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static com.android.server.power.hint.Flags.resetOnForkEnabled;
25 
26 import android.Manifest;
27 import android.adpf.ISessionManager;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.ActivityManagerInternal;
32 import android.app.StatsManager;
33 import android.app.UidObserver;
34 import android.content.Context;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageManager;
37 import android.hardware.power.ChannelConfig;
38 import android.hardware.power.CpuHeadroomParams;
39 import android.hardware.power.CpuHeadroomResult;
40 import android.hardware.power.GpuHeadroomParams;
41 import android.hardware.power.GpuHeadroomResult;
42 import android.hardware.power.IPower;
43 import android.hardware.power.SessionConfig;
44 import android.hardware.power.SessionMode;
45 import android.hardware.power.SessionTag;
46 import android.hardware.power.SupportInfo;
47 import android.hardware.power.WorkDuration;
48 import android.os.Binder;
49 import android.os.CpuHeadroomParamsInternal;
50 import android.os.GpuHeadroomParamsInternal;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.IHintManager;
54 import android.os.IHintSession;
55 import android.os.Looper;
56 import android.os.Message;
57 import android.os.PerformanceHintManager;
58 import android.os.Process;
59 import android.os.RemoteException;
60 import android.os.ServiceManager;
61 import android.os.ServiceSpecificException;
62 import android.os.SessionCreationConfig;
63 import android.os.SystemProperties;
64 import android.os.UserHandle;
65 import android.system.Os;
66 import android.system.OsConstants;
67 import android.text.TextUtils;
68 import android.util.ArrayMap;
69 import android.util.ArraySet;
70 import android.util.IntArray;
71 import android.util.Slog;
72 import android.util.SparseIntArray;
73 import android.util.StatsEvent;
74 
75 import com.android.internal.annotations.GuardedBy;
76 import com.android.internal.annotations.VisibleForTesting;
77 import com.android.internal.util.DumpUtils;
78 import com.android.internal.util.FrameworkStatsLog;
79 import com.android.internal.util.Preconditions;
80 import com.android.server.FgThread;
81 import com.android.server.LocalServices;
82 import com.android.server.ServiceThread;
83 import com.android.server.SystemService;
84 import com.android.server.utils.Slogf;
85 
86 import java.io.BufferedReader;
87 import java.io.FileDescriptor;
88 import java.io.FileReader;
89 import java.io.IOException;
90 import java.io.PrintWriter;
91 import java.lang.reflect.Field;
92 import java.util.ArrayList;
93 import java.util.Arrays;
94 import java.util.HashMap;
95 import java.util.LinkedList;
96 import java.util.List;
97 import java.util.Map;
98 import java.util.NoSuchElementException;
99 import java.util.Objects;
100 import java.util.PriorityQueue;
101 import java.util.Set;
102 import java.util.TreeMap;
103 import java.util.concurrent.TimeUnit;
104 import java.util.concurrent.atomic.AtomicBoolean;
105 import java.util.regex.Matcher;
106 import java.util.regex.Pattern;
107 
108 /** An hint service implementation that runs in System Server process. */
109 public final class HintManagerService extends SystemService {
110     private static final String TAG = "HintManagerService";
111     private static final boolean DEBUG = false;
112 
113     private static final int EVENT_CLEAN_UP_UID = 3;
114     @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
115 
116     // example: cpu  2255 34 2290 22625563 6290 127 456
117     private static final Pattern PROC_STAT_CPU_TIME_TOTAL_PATTERN =
118             Pattern.compile("cpu\\s+(?<user>[0-9]+)\\s(?<nice>[0-9]+).+");
119 
120     @VisibleForTesting final long mHintSessionPreferredRate;
121 
122     @VisibleForTesting static final int MAX_GRAPHICS_PIPELINE_THREADS_COUNT = 5;
123     private static final int DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT = 5;
124     private static final int DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS = 50;
125 
126     // Multi-level map storing all active AppHintSessions.
127     // First level is keyed by the UID of the client process creating the session.
128     // Second level is keyed by an IBinder passed from client process. This is used to observe
129     // when the process exits. The client generally uses the same IBinder object across multiple
130     // sessions, so the value is a set of AppHintSessions.
131     @GuardedBy("mLock")
132     private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
133 
134     // Multi-level map storing all the channel binder token death listeners.
135     // First level is keyed by the UID of the client process owning the channel.
136     // Second level is the tgid of the process, which will often just be size one.
137     // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an
138     // object that listens for the death notification of the binder token that was provided by
139     // that client when it created the channel, so we can detect when the client process dies.
140     @GuardedBy("mChannelMapLock")
141     private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
142 
143     /*
144      * Multi-level map storing the session statistics since last pull from StatsD.
145      * The first level is keyed by the UID of the process owning the session.
146      * The second level is keyed by the tag of the session. The point of separating different
147      * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different
148      * behaviors.
149      */
150     @GuardedBy("mSessionSnapshotMapLock")
151     private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap;
152 
153     /*
154      * App UID to Thread mapping.
155      * Thread is a sub class bookkeeping TID, thread mode (especially graphics pipeline mode)
156      * This is to bookkeep and track the thread usage.
157      */
158     @GuardedBy("mThreadsUsageObject")
159     private ArrayMap<Integer, ArraySet<ThreadUsageTracker>> mThreadsUsageMap;
160 
161     /** Lock to protect mActiveSessions and the UidObserver. */
162     private final Object mLock = new Object();
163 
164     /** Lock to protect mChannelMap. */
165     private final Object mChannelMapLock = new Object();
166 
167     /*
168      * Lock to protect mSessionSnapshotMap.
169      * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided.
170      * We should grab these separately.
171      * When we need to have nested acquisitions, we should always follow the order of acquiring
172      * mSessionSnapshotMapLock first then mLock.
173      */
174     private final Object mSessionSnapshotMapLock = new Object();
175 
176     /** Lock to protect mThreadsUsageMap. */
177     private final Object mThreadsUsageObject = new Object();
178 
179     @GuardedBy("mNonIsolatedTidsLock")
180     private final Map<Integer, Set<Long>> mNonIsolatedTids;
181 
182     private final Object mNonIsolatedTidsLock = new Object();
183 
184     @VisibleForTesting final MyUidObserver mUidObserver;
185 
186     private final NativeWrapper mNativeWrapper;
187     private final CleanUpHandler mCleanUpHandler;
188 
189     private final ActivityManagerInternal mAmInternal;
190 
191     private final Context mContext;
192 
193     private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true);
194 
195     private final IPower mPowerHal;
196     private int mPowerHalVersion;
197     private SupportInfo mSupportInfo = null;
198     private final PackageManager mPackageManager;
199 
200     private boolean mUsesFmq;
201 
202     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
203     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
204     private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
205     private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
206     private static final String PROPERTY_CHECK_HEADROOM_AFFINITY =
207             "persist.hms.check_headroom_affinity";
208     private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS =
209             "persist.hms.check_headroom_proc_stat_min_millis";
210     private static final String PROPERTY_CPU_HEADROOM_TID_MAX_CNT =
211             "persist.hms.cpu_headroom_tid_max_cnt";
212     private Boolean mFMQUsesIntegratedEventFlag = false;
213 
214     private final Object mCpuHeadroomLock = new Object();
215     @VisibleForTesting
216     final float mJiffyMillis;
217     private final boolean mCheckHeadroomTid;
218     private final boolean mCheckHeadroomAffinity;
219     private final int mCheckHeadroomProcStatMinMillis;
220     private final int mCpuHeadroomMaxTidCnt;
221     @GuardedBy("mCpuHeadroomLock")
222     private long mLastCpuUserModeTimeCheckedMillis = 0;
223     @GuardedBy("mCpuHeadroomLock")
224     private long mLastCpuUserModeJiffies = 0;
225     @GuardedBy("mCpuHeadroomLock")
226     private final Map<Integer, Long> mUidToLastUserModeJiffies;
227     @VisibleForTesting
228     private String mProcStatFilePathOverride = null;
229     @VisibleForTesting
230     private boolean mEnforceCpuHeadroomUserModeCpuTimeCheck = false;
231 
232     private ISessionManager mSessionManager;
233 
234     // this cache tracks the expiration time of the items and performs cleanup on lookup
235     private static class HeadroomCache<K, V> {
236         final List<HeadroomCacheItem> mItemList;
237         final Map<K, HeadroomCacheItem> mKeyItemMap;
238         final long mItemExpDurationMillis;
239 
240         class HeadroomCacheItem {
241             long mExpTime;
242             K mKey;
243             V mValue;
244 
HeadroomCacheItem(K k, V v)245             HeadroomCacheItem(K k, V v) {
246                 mExpTime = System.currentTimeMillis() + mItemExpDurationMillis;
247                 mKey = k;
248                 mValue = v;
249             }
250 
isExpired()251             boolean isExpired() {
252                 return mExpTime <= System.currentTimeMillis();
253             }
254         }
255 
add(K key, V value)256         void add(K key, V value) {
257             if (mKeyItemMap.containsKey(key)) {
258                 final HeadroomCacheItem item = mKeyItemMap.get(key);
259                 mItemList.remove(item);
260             }
261             final HeadroomCacheItem item = new HeadroomCacheItem(key, value);
262             mItemList.add(item);
263             mKeyItemMap.put(key, item);
264         }
265 
get(K key)266         V get(K key) {
267             while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) {
268                 mKeyItemMap.remove(mItemList.removeFirst().mKey);
269             }
270             final HeadroomCacheItem item = mKeyItemMap.get(key);
271             if (item == null) {
272                 return null;
273             }
274             return item.mValue;
275         }
276 
HeadroomCache(int size, long expDurationMillis)277         HeadroomCache(int size, long expDurationMillis) {
278             mItemList = new LinkedList<>();
279             mKeyItemMap = new ArrayMap<>(size);
280             mItemExpDurationMillis = expDurationMillis;
281         }
282     }
283 
284     @GuardedBy("mCpuHeadroomLock")
285     private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
286 
287     private final Object mGpuHeadroomLock = new Object();
288 
289     @GuardedBy("mGpuHeadroomLock")
290     private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
291 
292     // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
293     private final int mDefaultCpuHeadroomCalculationWindowMillis;
294     private final int mDefaultGpuHeadroomCalculationWindowMillis;
295 
296     @VisibleForTesting
297     final IHintManager.Stub mService = new BinderService();
298 
HintManagerService(Context context)299     public HintManagerService(Context context) {
300         this(context, new Injector());
301     }
302 
303     @VisibleForTesting
HintManagerService(Context context, Injector injector)304     HintManagerService(Context context, Injector injector) {
305         super(context);
306         mContext = context;
307         if (powerhintThreadCleanup()) {
308             mCleanUpHandler = new CleanUpHandler(createCleanUpThread().getLooper());
309             mNonIsolatedTids = new HashMap<>();
310         } else {
311             mCleanUpHandler = null;
312             mNonIsolatedTids = null;
313         }
314         if (adpfSessionTag()) {
315             mPackageManager = mContext.getPackageManager();
316         } else {
317             mPackageManager = null;
318         }
319         mActiveSessions = new ArrayMap<>();
320         mChannelMap = new ArrayMap<>();
321         mSessionSnapshotMap = new ArrayMap<>();
322         mThreadsUsageMap = new ArrayMap<>();
323         mNativeWrapper = injector.createNativeWrapper();
324         mNativeWrapper.halInit();
325         mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
326         mUidObserver = new MyUidObserver();
327         mAmInternal = Objects.requireNonNull(
328                 LocalServices.getService(ActivityManagerInternal.class));
329         mPowerHal = injector.createIPower();
330         mPowerHalVersion = 0;
331         mUsesFmq = false;
332         if (mPowerHal != null) {
333             try {
334                 mSupportInfo = getSupportInfo();
335             } catch (RemoteException e) {
336                 throw new IllegalStateException("Could not contact PowerHAL!", e);
337             }
338         }
339         mDefaultCpuHeadroomCalculationWindowMillis =
340                 new CpuHeadroomParamsInternal().calculationWindowMillis;
341         mDefaultGpuHeadroomCalculationWindowMillis =
342                 new GpuHeadroomParamsInternal().calculationWindowMillis;
343         if (mSupportInfo.headroom.isCpuSupported) {
344             mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
345             mUidToLastUserModeJiffies = new ArrayMap<>();
346             long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
347             mJiffyMillis = 1000.0f / jiffyHz;
348             mCheckHeadroomTid = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true);
349             mCheckHeadroomAffinity = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY,
350                     true);
351             mCheckHeadroomProcStatMinMillis = SystemProperties.getInt(
352                     PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS,
353                     DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS);
354             mCpuHeadroomMaxTidCnt = Math.min(SystemProperties.getInt(
355                     PROPERTY_CPU_HEADROOM_TID_MAX_CNT, DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT),
356                     mSupportInfo.headroom.cpuMaxTidCount);
357         } else {
358             mCpuHeadroomCache = null;
359             mUidToLastUserModeJiffies = null;
360             mJiffyMillis = 0.0f;
361             mCheckHeadroomTid = true;
362             mCheckHeadroomAffinity = true;
363             mCheckHeadroomProcStatMinMillis = 0;
364             mCpuHeadroomMaxTidCnt = 0;
365         }
366         if (mSupportInfo.headroom.isGpuSupported) {
367             mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
368         } else {
369             mGpuHeadroomCache = null;
370         }
371     }
372 
getSupportInfo()373     SupportInfo getSupportInfo() throws RemoteException {
374         try {
375             mPowerHalVersion = mPowerHal.getInterfaceVersion();
376             if (mPowerHalVersion >= 6) {
377                 return mPowerHal.getSupportInfo();
378             }
379         } catch (RemoteException e) {
380             throw new IllegalStateException("Could not contact PowerHAL!", e);
381         }
382 
383         SupportInfo supportInfo = new SupportInfo();
384         supportInfo.usesSessions = isHintSessionSupported();
385         // Global boosts & modes aren't currently relevant for HMS clients
386         supportInfo.boosts = 0;
387         supportInfo.modes = 0;
388         supportInfo.sessionHints = 0;
389         supportInfo.sessionModes = 0;
390         supportInfo.sessionTags = 0;
391 
392         supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
393         supportInfo.headroom.isCpuSupported = false;
394         supportInfo.headroom.isGpuSupported = false;
395 
396         supportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
397         if (isHintSessionSupported()) {
398             if (mPowerHalVersion == 4) {
399                 // Assume we support the V4 hints & modes unless specified
400                 // otherwise; this is to avoid breaking backwards compat
401                 // since we historically just assumed they were.
402                 supportInfo.sessionHints = 31; // first 5 bits are ones
403             }
404             if (mPowerHalVersion == 5) {
405                 // Assume we support the V5 hints & modes unless specified
406                 // otherwise; this is to avoid breaking backwards compat
407                 // since we historically just assumed they were.
408 
409                 // Hal V5 has 8 modes, all of which it assumes are supported,
410                 // so we represent that by having the first 8 bits set
411                 supportInfo.sessionHints = 255; // first 8 bits are ones
412                 // Hal V5 has 1 mode which it assumes is supported, so we
413                 // represent that by having the first bit set
414                 supportInfo.sessionModes = 1;
415                 // Hal V5 has 5 tags, all of which it assumes are supported,
416                 // so we represent that by having the first 5 bits set
417                 supportInfo.sessionTags = 31;
418             }
419         }
420         return supportInfo;
421     }
422 
423     @VisibleForTesting
setProcStatPathOverride(String override)424     void setProcStatPathOverride(String override) {
425         mProcStatFilePathOverride = override;
426         mEnforceCpuHeadroomUserModeCpuTimeCheck = true;
427     }
428 
tooManyPipelineThreads(int uid)429     private boolean tooManyPipelineThreads(int uid) {
430         synchronized (mThreadsUsageObject) {
431             ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(uid);
432             int graphicsPipelineThreadCount = 0;
433             if (threadsSet != null) {
434                 // We count the graphics pipeline threads that are
435                 // *not* in this session, since those in this session
436                 // will be replaced. Then if the count plus the new tids
437                 // is over max available graphics pipeline threads we raise
438                 // an exception.
439                 for (ThreadUsageTracker t : threadsSet) {
440                     if (t.isGraphicsPipeline()) {
441                         graphicsPipelineThreadCount++;
442                     }
443                 }
444                 if (graphicsPipelineThreadCount > MAX_GRAPHICS_PIPELINE_THREADS_COUNT) {
445                     return true;
446                 }
447             }
448             return false;
449         }
450     }
451 
createCleanUpThread()452     private ServiceThread createCleanUpThread() {
453         final ServiceThread handlerThread = new ServiceThread(TAG,
454                 Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/);
455         handlerThread.start();
456         return handlerThread;
457     }
458 
459     @VisibleForTesting
460     static class Injector {
createNativeWrapper()461         NativeWrapper createNativeWrapper() {
462             return new NativeWrapper();
463         }
createIPower()464         IPower createIPower() {
465             return IPower.Stub.asInterface(
466                 ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default"));
467         }
468     }
469 
470     private static class ThreadUsageTracker {
471         /*
472          * Thread object for tracking thread usage per UID
473          */
474         int mTid;
475         boolean mIsGraphicsPipeline;
476 
ThreadUsageTracker(int tid)477         ThreadUsageTracker(int tid) {
478             mTid = tid;
479             mIsGraphicsPipeline = false;
480         }
481 
ThreadUsageTracker(int tid, boolean isGraphicsPipeline)482         ThreadUsageTracker(int tid, boolean isGraphicsPipeline) {
483             mTid = tid;
484             mIsGraphicsPipeline = isGraphicsPipeline;
485         }
486 
getTid()487         public int getTid() {
488             return mTid;
489         }
490 
isGraphicsPipeline()491         public boolean isGraphicsPipeline() {
492             return mIsGraphicsPipeline;
493         }
494 
setGraphicsPipeline(boolean isGraphicsPipeline)495         public void setGraphicsPipeline(boolean isGraphicsPipeline) {
496             mIsGraphicsPipeline = isGraphicsPipeline;
497         }
498     }
499 
500     private class AppHintSessionSnapshot {
501         /*
502          * Per-Uid and Per-SessionTag snapshot that tracks metrics including
503          * number of created sessions, number of power efficienct sessions, and
504          * maximum number of threads in a session.
505          * Given that it's Per-SessionTag, each uid can have multiple snapshots.
506          */
507         int mCurrentSessionCount;
508         int mMaxConcurrentSession;
509         int mMaxThreadCount;
510         int mPowerEfficientSessionCount;
511         int mGraphicsPipelineSessionCount;
512 
513         final int mTargetDurationNsCountPQSize = 100;
514         PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
515 
516         class TargetDurationRecord implements Comparable<TargetDurationRecord> {
517             long mTargetDurationNs;
518             long mTimestamp;
519             int mCount;
TargetDurationRecord(long targetDurationNs)520             TargetDurationRecord(long targetDurationNs) {
521                 mTargetDurationNs = targetDurationNs;
522                 mTimestamp = System.currentTimeMillis();
523                 mCount = 1;
524             }
525 
526             @Override
compareTo(TargetDurationRecord t)527             public int compareTo(TargetDurationRecord t) {
528                 int tCount = t.getCount();
529                 int thisCount = this.getCount();
530                 // Here we sort in the order of number of count in ascending order.
531                 // i.e. the lowest count of target duration is at the head of the queue.
532                 // Upon same count, the tiebreaker is the timestamp, the older item will be at the
533                 // front of the queue.
534                 if (tCount == thisCount) {
535                     return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1;
536                 }
537                 return (tCount < thisCount) ? 1 : -1;
538             }
getTargetDurationNs()539             long getTargetDurationNs() {
540                 return mTargetDurationNs;
541             }
542 
getCount()543             int getCount() {
544                 return mCount;
545             }
546 
getTimestamp()547             long getTimestamp() {
548                 return mTimestamp;
549             }
550 
setCount(int count)551             void setCount(int count) {
552                 mCount = count;
553             }
554 
setTimestamp()555             void setTimestamp() {
556                 mTimestamp = System.currentTimeMillis();
557             }
558 
setTargetDurationNs(long targetDurationNs)559             void setTargetDurationNs(long targetDurationNs) {
560                 mTargetDurationNs = targetDurationNs;
561             }
562         }
563 
AppHintSessionSnapshot()564         AppHintSessionSnapshot() {
565             mCurrentSessionCount = 0;
566             mMaxConcurrentSession = 0;
567             mMaxThreadCount = 0;
568             mPowerEfficientSessionCount = 0;
569             mGraphicsPipelineSessionCount = 0;
570             mTargetDurationNsCountPQ = new PriorityQueue<>(1);
571         }
572 
updateUponSessionCreation(int threadCount, long targetDuration)573         void updateUponSessionCreation(int threadCount, long targetDuration) {
574             mCurrentSessionCount += 1;
575             mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount);
576             mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
577             updateTargetDurationNs(targetDuration);
578         }
579 
updateUponSessionClose()580         void updateUponSessionClose() {
581             mCurrentSessionCount -= 1;
582         }
583 
logPowerEfficientSession()584         void logPowerEfficientSession() {
585             mPowerEfficientSessionCount += 1;
586         }
587 
logGraphicsPipelineSession()588         void logGraphicsPipelineSession() {
589             mGraphicsPipelineSessionCount += 1;
590         }
591 
updateThreadCount(int threadCount)592         void updateThreadCount(int threadCount) {
593             mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
594         }
595 
updateTargetDurationNs(long targetDurationNs)596         void updateTargetDurationNs(long targetDurationNs) {
597             for (TargetDurationRecord t : mTargetDurationNsCountPQ) {
598                 if (t.getTargetDurationNs() == targetDurationNs) {
599                     t.setCount(t.getCount() + 1);
600                     t.setTimestamp();
601                     return;
602                 }
603             }
604             if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) {
605                 mTargetDurationNsCountPQ.poll();
606             }
607             mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs));
608         }
609 
getMaxConcurrentSession()610         int getMaxConcurrentSession() {
611             return mMaxConcurrentSession;
612         }
613 
getMaxThreadCount()614         int getMaxThreadCount() {
615             return mMaxThreadCount;
616         }
617 
getPowerEfficientSessionCount()618         int getPowerEfficientSessionCount() {
619             return mPowerEfficientSessionCount;
620         }
621 
getGraphicsPipelineSessionCount()622         int getGraphicsPipelineSessionCount() {
623             return mGraphicsPipelineSessionCount;
624         }
625 
targetDurationNsList()626         long[] targetDurationNsList() {
627             final int listSize = 5;
628             long[] targetDurations = new long[listSize];
629             while (mTargetDurationNsCountPQ.size() > listSize) {
630                 mTargetDurationNsCountPQ.poll();
631             }
632             for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) {
633                 targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs();
634             }
635             return targetDurations;
636         }
637     }
isHintSessionSupported()638     private boolean isHintSessionSupported() {
639         return mHintSessionPreferredRate != -1;
640     }
641 
642     @Override
onStart()643     public void onStart() {
644         publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService);
645     }
646 
647     @Override
onBootPhase(int phase)648     public void onBootPhase(int phase) {
649         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
650             systemReady();
651         }
652         if (phase == SystemService.PHASE_BOOT_COMPLETED) {
653             registerStatsCallbacks();
654         }
655     }
656 
systemReady()657     private void systemReady() {
658         Slogf.v(TAG, "Initializing HintManager service...");
659         try {
660             ActivityManager.getService().registerUidObserver(mUidObserver,
661                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
662                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
663         } catch (RemoteException e) {
664             // ignored; both services live in system_server
665         }
666 
667     }
668 
registerStatsCallbacks()669     private void registerStatsCallbacks() {
670         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
671         statsManager.setPullAtomCallback(
672                 FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
673                 null, // use default PullAtomMetadata values
674                 DIRECT_EXECUTOR,
675                 this::onPullAtom);
676         statsManager.setPullAtomCallback(
677                 FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
678                 null, // use default PullAtomMetadata values
679                 DIRECT_EXECUTOR,
680                 this::onPullAtom);
681     }
682 
onPullAtom(int atomTag, @NonNull List<StatsEvent> data)683     private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
684         if (atomTag == FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO) {
685             final boolean isSurfaceFlingerUsingCpuHint =
686                     SystemProperties.getBoolean(PROPERTY_SF_ENABLE_CPU_HINT, false);
687             final boolean isHwuiHintManagerEnabled =
688                     SystemProperties.getBoolean(PROPERTY_HWUI_ENABLE_HINT_MANAGER, false);
689 
690             data.add(FrameworkStatsLog.buildStatsEvent(
691                     FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
692                     isSurfaceFlingerUsingCpuHint,
693                     isHwuiHintManagerEnabled,
694                     getFmqUsage()));
695         }
696         if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) {
697             synchronized (mSessionSnapshotMapLock) {
698                 for (int i = 0; i < mSessionSnapshotMap.size(); ++i) {
699                     final int uid = mSessionSnapshotMap.keyAt(i);
700                     final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
701                             mSessionSnapshotMap.valueAt(i);
702                     for (int j = 0; j < sessionSnapshots.size(); ++j) {
703                         final int sessionTag = sessionSnapshots.keyAt(j);
704                         final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j);
705                         data.add(FrameworkStatsLog.buildStatsEvent(
706                                 FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
707                                 uid,
708                                 sessionTag,
709                                 sessionSnapshot.getMaxConcurrentSession(),
710                                 sessionSnapshot.getMaxThreadCount(),
711                                 sessionSnapshot.getPowerEfficientSessionCount(),
712                                 sessionSnapshot.targetDurationNsList()
713                         ));
714                     }
715                 }
716             }
717             restoreSessionSnapshot();
718         }
719         return android.app.StatsManager.PULL_SUCCESS;
720     }
721 
getFmqUsage()722     private int getFmqUsage() {
723         if (mUsesFmq) {
724             return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED;
725         } else if (mPowerHalVersion < 5) {
726             return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET;
727         } else {
728             return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED;
729         }
730     }
731 
restoreSessionSnapshot()732     private void restoreSessionSnapshot() {
733         // clean up snapshot map and rebuild with current active sessions
734         synchronized (mSessionSnapshotMapLock) {
735             mSessionSnapshotMap.clear();
736             synchronized (mLock) {
737                 for (int i = 0; i < mActiveSessions.size(); i++) {
738                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
739                             mActiveSessions.valueAt(i);
740                     for (int j = 0; j < tokenMap.size(); j++) {
741                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
742                         for (int k = 0; k < sessionSet.size(); ++k) {
743                             AppHintSession appHintSession = sessionSet.valueAt(k);
744                             final int tag = appHintSession.getTag();
745                             final int uid = appHintSession.getUid();
746                             final long targetDuationNs =
747                                     appHintSession.getTargetDurationNs();
748                             final int threadCount = appHintSession.getThreadIds().length;
749                             ArrayMap<Integer, AppHintSessionSnapshot> snapshots =
750                                     mSessionSnapshotMap.get(uid);
751                             if (snapshots == null) {
752                                 snapshots = new ArrayMap<>();
753                                 mSessionSnapshotMap.put(uid, snapshots);
754                             }
755                             AppHintSessionSnapshot snapshot = snapshots.get(tag);
756                             if (snapshot == null) {
757                                 snapshot = new AppHintSessionSnapshot();
758                                 snapshots.put(tag, snapshot);
759                             }
760                             snapshot.updateUponSessionCreation(threadCount,
761                                     targetDuationNs);
762                         }
763                     }
764                 }
765             }
766         }
767     }
768 
769     /**
770      * Wrapper around the static-native methods from native.
771      *
772      * This class exists to allow us to mock static native methods in our tests. If mocking static
773      * methods becomes easier than this in the future, we can delete this class.
774      */
775     @VisibleForTesting
776     public static class NativeWrapper {
nativeInit()777         private native void nativeInit();
778 
nativeGetHintSessionPreferredRate()779         private static native long nativeGetHintSessionPreferredRate();
780 
nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)781         private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
782                 long durationNanos);
783 
nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)784         private static native long nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids,
785                 long durationNanos, int tag, SessionConfig config);
786 
nativePauseHintSession(long halPtr)787         private static native void nativePauseHintSession(long halPtr);
788 
nativeResumeHintSession(long halPtr)789         private static native void nativeResumeHintSession(long halPtr);
790 
nativeCloseHintSession(long halPtr)791         private static native void nativeCloseHintSession(long halPtr);
792 
nativeUpdateTargetWorkDuration( long halPtr, long targetDurationNanos)793         private static native void nativeUpdateTargetWorkDuration(
794                 long halPtr, long targetDurationNanos);
795 
nativeReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)796         private static native void nativeReportActualWorkDuration(
797                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
798 
nativeSendHint(long halPtr, int hint)799         private static native void nativeSendHint(long halPtr, int hint);
800 
nativeSetThreads(long halPtr, int[] tids)801         private static native void nativeSetThreads(long halPtr, int[] tids);
802 
nativeSetMode(long halPtr, int mode, boolean enabled)803         private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
804 
nativeReportActualWorkDuration( long halPtr, WorkDuration[] workDurations)805         private static native void nativeReportActualWorkDuration(
806                 long halPtr, WorkDuration[] workDurations);
807 
808         /** Wrapper for HintManager.nativeInit */
halInit()809         public void halInit() {
810             nativeInit();
811         }
812 
813         /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
halGetHintSessionPreferredRate()814         public long halGetHintSessionPreferredRate() {
815             return nativeGetHintSessionPreferredRate();
816         }
817 
818         /** Wrapper for HintManager.nativeCreateHintSession */
halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)819         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
820             return nativeCreateHintSession(tgid, uid, tids, durationNanos);
821         }
822 
823         /** Wrapper for HintManager.nativeCreateHintSessionWithConfig */
halCreateHintSessionWithConfig( int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)824         public long halCreateHintSessionWithConfig(
825                 int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config) {
826             return nativeCreateHintSessionWithConfig(tgid, uid, tids, durationNanos, tag, config);
827         }
828 
829         /** Wrapper for HintManager.nativePauseHintSession */
halPauseHintSession(long halPtr)830         public void halPauseHintSession(long halPtr) {
831             nativePauseHintSession(halPtr);
832         }
833 
834         /** Wrapper for HintManager.nativeResumeHintSession */
halResumeHintSession(long halPtr)835         public void halResumeHintSession(long halPtr) {
836             nativeResumeHintSession(halPtr);
837         }
838 
839         /** Wrapper for HintManager.nativeCloseHintSession */
halCloseHintSession(long halPtr)840         public void halCloseHintSession(long halPtr) {
841             nativeCloseHintSession(halPtr);
842         }
843 
844         /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */
halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos)845         public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
846             nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos);
847         }
848 
849         /** Wrapper for HintManager.nativeReportActualWorkDuration */
halReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)850         public void halReportActualWorkDuration(
851                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
852             nativeReportActualWorkDuration(halPtr, actualDurationNanos,
853                     timeStampNanos);
854         }
855 
856         /** Wrapper for HintManager.sendHint */
halSendHint(long halPtr, int hint)857         public void halSendHint(long halPtr, int hint) {
858             nativeSendHint(halPtr, hint);
859         }
860 
861         /** Wrapper for HintManager.nativeSetThreads */
halSetThreads(long halPtr, int[] tids)862         public void halSetThreads(long halPtr, int[] tids) {
863             nativeSetThreads(halPtr, tids);
864         }
865 
866         /** Wrapper for HintManager.setMode */
halSetMode(long halPtr, int mode, boolean enabled)867         public void halSetMode(long halPtr, int mode, boolean enabled) {
868             nativeSetMode(halPtr, mode, enabled);
869         }
870 
871         /** Wrapper for HintManager.nativeReportActualWorkDuration */
halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations)872         public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
873             nativeReportActualWorkDuration(halPtr, workDurations);
874         }
875     }
876 
877     @VisibleForTesting
878     final class MyUidObserver extends UidObserver {
879         @GuardedBy("mLock")
880         private final SparseIntArray mProcStatesCache = new SparseIntArray();
isUidForeground(int uid)881         public boolean isUidForeground(int uid) {
882             synchronized (mLock) {
883                 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
884                         <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
885             }
886         }
887 
888         @Override
onUidGone(int uid, boolean disabled)889         public void onUidGone(int uid, boolean disabled) {
890             FgThread.getHandler().post(() -> {
891                 synchronized (mLock) {
892                     mProcStatesCache.delete(uid);
893                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
894                     if (tokenMap == null) {
895                         return;
896                     }
897                     Slog.d(TAG, "Uid gone for " + uid);
898                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
899                         // Will remove the session from tokenMap
900                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
901                         IntArray closedSessionsForSf = new IntArray();
902                         // Batch the closure call to SF for all the sessions that die
903                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
904                             AppHintSession session = sessionSet.valueAt(j);
905                             if (session.isTrackedBySf()) {
906                                 // Mark it as untracked so we don't untrack again on close
907                                 session.setTrackedBySf(false);
908                                 closedSessionsForSf.add(session.getSessionId());
909                             }
910                         }
911                         if (mSessionManager != null) {
912                             try {
913                                 mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray());
914                             } catch (RemoteException e) {
915                                 Slog.e(TAG, "Failed to communicate with SessionManager");
916                             }
917                         }
918                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
919                             sessionSet.valueAt(j).close();
920                         }
921                     }
922                 }
923                 synchronized (mChannelMapLock) {
924                     // Clean up the uid's session channels
925                     final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
926                     if (uidMap != null) {
927                         for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) {
928                             entry.getValue().closeChannel();
929                         }
930                         mChannelMap.remove(uid);
931                     }
932                 }
933                 synchronized (mCpuHeadroomLock) {
934                     if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) {
935                         mUidToLastUserModeJiffies.remove(uid);
936                     }
937                 }
938             });
939         }
940 
941         /**
942          * The IUidObserver callback is called from the system_server, so it'll be a direct function
943          * call from ActivityManagerService. Do not do heavy logic here.
944          */
945         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)946         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
947             FgThread.getHandler().post(() -> {
948                 synchronized (mLock) {
949                     boolean shouldCleanup = false;
950                     if (mPowerHalVersion >= 4 && powerhintThreadCleanup()) {
951                         int prevProcState = mProcStatesCache.get(uid, Integer.MAX_VALUE);
952                         shouldCleanup =
953                                 prevProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
954                                         && procState
955                                         > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
956                     }
957 
958                     mProcStatesCache.put(uid, procState);
959                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
960                     if (tokenMap == null) {
961                         return;
962                     }
963                     if (shouldCleanup && powerhintThreadCleanup()) {
964                         final Message msg = mCleanUpHandler.obtainMessage(EVENT_CLEAN_UP_UID,
965                                 uid);
966                         mCleanUpHandler.sendMessageDelayed(msg, CLEAN_UP_UID_DELAY_MILLIS);
967                         Slog.d(TAG, "Sent cleanup message for uid " + uid);
968                     }
969                     boolean shouldAllowUpdate = isUidForeground(uid);
970                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
971                         final ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
972                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
973                             sessionSet.valueAt(j).updateHintAllowedByProcState(shouldAllowUpdate);
974                         }
975                     }
976                 }
977             });
978         }
979     }
980 
981     /**
982      * Creates a channel item in the channel map if one does not exist, then returns
983      * the entry in the channel map.
984      */
getOrCreateMappedChannelItem(int tgid, int uid, IBinder token)985     public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) {
986         synchronized (mChannelMapLock) {
987             if (!mChannelMap.containsKey(uid)) {
988                 mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>());
989             }
990             TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
991             if (!map.containsKey(tgid)) {
992                 ChannelItem item = new ChannelItem(tgid, uid, token);
993                 item.openChannel();
994                 map.put(tgid, item);
995             }
996             return map.get(tgid);
997         }
998     }
999 
1000     /**
1001      * This removes an entry in the binder token callback map when a channel is closed,
1002      * and unregisters its callbacks.
1003      */
removeChannelItem(Integer tgid, Integer uid)1004     public void removeChannelItem(Integer tgid, Integer uid) {
1005         synchronized (mChannelMapLock) {
1006             TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
1007             if (map != null) {
1008                 ChannelItem item = map.get(tgid);
1009                 if (item != null) {
1010                     item.closeChannel();
1011                     map.remove(tgid);
1012                 }
1013                 if (map.isEmpty()) {
1014                     mChannelMap.remove(uid);
1015                 }
1016             }
1017         }
1018     }
1019 
1020     /**
1021      * Manages the lifecycle of a single channel. This includes caching the channel descriptor,
1022      * receiving binder token death notifications, and handling cleanup on uid termination. There
1023      * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens
1024      * when a ChannelItem enters the map, while destruction happens when it leaves the map.
1025      */
1026     private class ChannelItem implements IBinder.DeathRecipient {
1027         @Override
binderDied()1028         public void binderDied() {
1029             removeChannelItem(mTgid, mUid);
1030         }
1031 
ChannelItem(int tgid, int uid, IBinder token)1032         ChannelItem(int tgid, int uid, IBinder token) {
1033             this.mTgid = tgid;
1034             this.mUid = uid;
1035             this.mToken = token;
1036             this.mLinked = false;
1037             this.mConfig = null;
1038         }
1039 
closeChannel()1040         public void closeChannel() {
1041             if (mLinked) {
1042                 mToken.unlinkToDeath(this, 0);
1043                 mLinked = false;
1044             }
1045             if (mConfig != null) {
1046                 try {
1047                     mPowerHal.closeSessionChannel(mTgid, mUid);
1048                 } catch (RemoteException e) {
1049                     throw new IllegalStateException("Failed to close session channel!", e);
1050                 }
1051                 mConfig = null;
1052             }
1053         }
1054 
openChannel()1055         public void openChannel() {
1056             if (!mLinked) {
1057                 try {
1058                     mToken.linkToDeath(this, 0);
1059                 } catch (RemoteException e) {
1060                     throw new IllegalStateException("Client already dead", e);
1061                 }
1062                 mLinked = true;
1063             }
1064             if (mConfig == null) {
1065                 try {
1066                     // This method uses PowerHAL directly through the SDK,
1067                     // to avoid needing to pass the ChannelConfig through JNI.
1068                     mConfig = mPowerHal.getSessionChannel(mTgid, mUid);
1069                 } catch (RemoteException e) {
1070                     removeChannelItem(mTgid, mUid);
1071                     throw new IllegalStateException("Failed to create session channel!", e);
1072                 }
1073             }
1074         }
1075 
getConfig()1076         ChannelConfig getConfig() {
1077             return mConfig;
1078         }
1079 
1080         // To avoid accidental double-linking / unlinking
1081         boolean mLinked;
1082         final int mTgid;
1083         final int mUid;
1084         final IBinder mToken;
1085         ChannelConfig mConfig;
1086     }
1087 
1088     final class CleanUpHandler extends Handler {
1089         // status of processed tid used for caching
1090         private static final int TID_NOT_CHECKED = 0;
1091         private static final int TID_PASSED_CHECK = 1;
1092         private static final int TID_EXITED = 2;
1093 
CleanUpHandler(Looper looper)1094         CleanUpHandler(Looper looper) {
1095             super(looper);
1096         }
1097 
1098         @Override
handleMessage(Message msg)1099         public void handleMessage(Message msg) {
1100             if (msg.what == EVENT_CLEAN_UP_UID) {
1101                 if (hasEqualMessages(msg.what, msg.obj)) {
1102                     removeEqualMessages(msg.what, msg.obj);
1103                     final Message newMsg = obtainMessage(msg.what, msg.obj);
1104                     sendMessageDelayed(newMsg, CLEAN_UP_UID_DELAY_MILLIS);
1105                     Slog.d(TAG, "Duplicate messages for " + msg.obj);
1106                     return;
1107                 }
1108                 Slog.d(TAG, "Starts cleaning for " + msg.obj);
1109                 final int uid = (int) msg.obj;
1110                 boolean isForeground = mUidObserver.isUidForeground(uid);
1111                 // store all sessions in a list and release the global lock
1112                 // we don't need to worry about stale data or racing as the session is synchronized
1113                 // itself and will perform its own closed status check in setThreads call
1114                 final List<AppHintSession> sessions;
1115                 synchronized (mLock) {
1116                     final ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
1117                             mActiveSessions.get(uid);
1118                     if (tokenMap == null || tokenMap.isEmpty()) {
1119                         return;
1120                     }
1121                     sessions = new ArrayList<>(tokenMap.size());
1122                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
1123                         final ArraySet<AppHintSession> set = tokenMap.valueAt(i);
1124                         for (int j = set.size() - 1; j >= 0; j--) {
1125                             sessions.add(set.valueAt(j));
1126                         }
1127                     }
1128                 }
1129                 final long[] durationList = new long[sessions.size()];
1130                 final int[] invalidTidCntList = new int[sessions.size()];
1131                 final SparseIntArray checkedTids = new SparseIntArray();
1132                 int[] totalTidCnt = new int[1];
1133                 for (int i = sessions.size() - 1; i >= 0; i--) {
1134                     final AppHintSession session = sessions.get(i);
1135                     final long start = System.nanoTime();
1136                     try {
1137                         final int invalidCnt = cleanUpSession(session, checkedTids, totalTidCnt);
1138                         final long elapsed = System.nanoTime() - start;
1139                         invalidTidCntList[i] = invalidCnt;
1140                         durationList[i] = elapsed;
1141                     } catch (Exception e) {
1142                         Slog.e(TAG, "Failed to clean up session " + session.mHalSessionPtr
1143                                 + " for UID " + session.mUid);
1144                     }
1145                 }
1146                 logCleanUpMetrics(uid, invalidTidCntList, durationList, sessions.size(),
1147                         totalTidCnt[0], isForeground);
1148             }
1149         }
1150 
logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt, int totalTidCnt, boolean isForeground)1151         private void logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt,
1152                 int totalTidCnt, boolean isForeground) {
1153             int maxInvalidTidCnt = Integer.MIN_VALUE;
1154             int totalInvalidTidCnt = 0;
1155             for (int i = 0; i < count.length; i++) {
1156                 totalInvalidTidCnt += count[i];
1157                 maxInvalidTidCnt = Math.max(maxInvalidTidCnt, count[i]);
1158             }
1159             if (DEBUG || totalInvalidTidCnt > 0) {
1160                 Arrays.sort(durationNsList);
1161                 long totalDurationNs = 0;
1162                 for (int i = 0; i < durationNsList.length; i++) {
1163                     totalDurationNs += durationNsList[i];
1164                 }
1165                 int totalDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(totalDurationNs);
1166                 int maxDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
1167                         durationNsList[durationNsList.length - 1]);
1168                 int minDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(durationNsList[0]);
1169                 int avgDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
1170                         totalDurationNs / durationNsList.length);
1171                 int th90DurationUs = (int) TimeUnit.NANOSECONDS.toMicros(
1172                         durationNsList[(int) (durationNsList.length * 0.9)]);
1173                 FrameworkStatsLog.write(FrameworkStatsLog.ADPF_HINT_SESSION_TID_CLEANUP, uid,
1174                         totalDurationUs, maxDurationUs, totalTidCnt, totalInvalidTidCnt,
1175                         maxInvalidTidCnt, sessionCnt, isForeground);
1176                 Slog.w(TAG,
1177                         "Invalid tid found for UID" + uid + " in " + totalDurationUs + "us:\n\t"
1178                                 + "count("
1179                                 + " session: " + sessionCnt
1180                                 + " totalTid: " + totalTidCnt
1181                                 + " maxInvalidTid: " + maxInvalidTidCnt
1182                                 + " totalInvalidTid: " + totalInvalidTidCnt + ")\n\t"
1183                                 + "time per session("
1184                                 + " min: " + minDurationUs + "us"
1185                                 + " max: " + maxDurationUs + "us"
1186                                 + " avg: " + avgDurationUs + "us"
1187                                 + " 90%: " + th90DurationUs + "us" + ")\n\t"
1188                                 + "isForeground: " + isForeground);
1189             }
1190         }
1191 
1192         // This will check if each TID currently linked to the session still exists. If it's
1193         // previously registered as not an isolated process, then it will run tkill(pid, tid, 0) to
1194         // verify that it's still running under the same pid. Otherwise, it will run
1195         // kill(tid, 0) to only check if it exists. The result will be cached in checkedTids
1196         // map with tid as the key and checked status as value.
cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total)1197         public int cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total) {
1198             if (session.isClosed() || session.isForcePaused()) {
1199                 return 0;
1200             }
1201             final int pid = session.mPid;
1202             final int[] tids = session.getTidsInternal();
1203             if (total != null && total.length == 1) {
1204                 total[0] += tids.length;
1205             }
1206             final IntArray filtered = new IntArray(tids.length);
1207             for (int i = 0; i < tids.length; i++) {
1208                 int tid = tids[i];
1209                 if (checkedTids.get(tid, 0) != TID_NOT_CHECKED) {
1210                     if (checkedTids.get(tid) == TID_PASSED_CHECK) {
1211                         filtered.add(tid);
1212                     }
1213                     continue;
1214                 }
1215                 // if it was registered as a non-isolated then we perform more restricted check
1216                 final boolean isNotIsolated;
1217                 synchronized (mNonIsolatedTidsLock) {
1218                     isNotIsolated = mNonIsolatedTids.containsKey(tid);
1219                 }
1220                 try {
1221                     if (isNotIsolated) {
1222                         Process.checkTid(pid, tid);
1223                     } else {
1224                         Process.checkPid(tid);
1225                     }
1226                     checkedTids.put(tid, TID_PASSED_CHECK);
1227                     filtered.add(tid);
1228                 } catch (NoSuchElementException e) {
1229                     checkedTids.put(tid, TID_EXITED);
1230                 } catch (Exception e) {
1231                     Slog.w(TAG, "Unexpected exception when checking TID " + tid + " under PID "
1232                             + pid + "(isolated: " + !isNotIsolated + ")", e);
1233                     // if anything unexpected happens then we keep it, but don't store it as checked
1234                     filtered.add(tid);
1235                 }
1236             }
1237             final int diff = tids.length - filtered.size();
1238             if (diff > 0) {
1239                 synchronized (session) {
1240                     // in case thread list is updated during the cleanup then we skip updating
1241                     // the session but just return the number for reporting purpose
1242                     final int[] newTids = session.getTidsInternal();
1243                     if (newTids.length != tids.length) {
1244                         Slog.d(TAG, "Skipped cleaning up the session as new tids are added");
1245                         return diff;
1246                     }
1247                     Arrays.sort(newTids);
1248                     Arrays.sort(tids);
1249                     if (!Arrays.equals(newTids, tids)) {
1250                         Slog.d(TAG, "Skipped cleaning up the session as new tids are updated");
1251                         return diff;
1252                     }
1253                     Slog.d(TAG, "Cleaned up " + diff + " invalid tids for session "
1254                             + session.mHalSessionPtr + " with UID " + session.mUid + "\n\t"
1255                             + "before: " + Arrays.toString(tids) + "\n\t"
1256                             + "after: " + filtered);
1257                     final int[] filteredTids = filtered.toArray();
1258                     if (filteredTids.length == 0) {
1259                         session.mShouldForcePause = true;
1260                         if (session.mUpdateAllowedByProcState) {
1261                             session.pause();
1262                         }
1263                     } else {
1264                         session.setThreadsInternal(filteredTids, false);
1265                     }
1266                 }
1267             }
1268             return diff;
1269         }
1270     }
1271 
1272     @VisibleForTesting
getBinderServiceInstance()1273     IHintManager.Stub getBinderServiceInstance() {
1274         return mService;
1275     }
1276 
1277     @VisibleForTesting
hasChannel(int tgid, int uid)1278     Boolean hasChannel(int tgid, int uid) {
1279         synchronized (mChannelMapLock) {
1280             TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
1281             if (uidMap != null) {
1282                 ChannelItem item = uidMap.get(tgid);
1283                 return item != null;
1284             }
1285             return false;
1286         }
1287     }
1288 
1289     // returns the first invalid tid or null if not found
checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated)1290     private Integer checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated) {
1291         // Make sure all tids belongs to the same UID (including isolated UID),
1292         // tids can belong to different application processes.
1293         List<Integer> isolatedPids = null;
1294         for (int i = 0; i < tids.length; i++) {
1295             int tid = tids[i];
1296             final String[] procStatusKeys = new String[]{
1297                     "Uid:",
1298                     "Tgid:"
1299             };
1300             long[] output = new long[procStatusKeys.length];
1301             Process.readProcLines("/proc/" + tid + "/status", procStatusKeys, output);
1302             int uidOfThreadId = (int) output[0];
1303             int pidOfThreadId = (int) output[1];
1304 
1305             // use PID check for non-isolated processes
1306             if (nonIsolated != null && pidOfThreadId == tgid) {
1307                 nonIsolated.add(tid);
1308                 continue;
1309             }
1310             // use UID check for isolated processes.
1311             if (uidOfThreadId == uid) {
1312                 continue;
1313             }
1314             // Only call into AM if the tid is either isolated or invalid
1315             if (isolatedPids == null) {
1316                 // To avoid deadlock, do not call into AMS if the call is from system.
1317                 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
1318                     return tid;
1319                 }
1320                 isolatedPids = mAmInternal.getIsolatedProcesses(uid);
1321                 if (isolatedPids == null) {
1322                     return tid;
1323                 }
1324             }
1325             if (isolatedPids.contains(pidOfThreadId)) {
1326                 continue;
1327             }
1328             return tid;
1329         }
1330         return null;
1331     }
1332 
formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid)1333     private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) {
1334         return "Tid" + invalidTid + " from list " + Arrays.toString(tids)
1335                 + " doesn't belong to the calling application " + callingUid;
1336     }
1337 
contains(final int[] array, final int target)1338     private boolean contains(final int[] array, final int target) {
1339         for (int element : array) {
1340             if (element == target) {
1341                 return true;
1342             }
1343         }
1344         return false;
1345     }
1346 
1347     @VisibleForTesting
1348     final class BinderService extends IHintManager.Stub {
1349         @Override
createHintSessionWithConfig( @onNull IBinder token, @SessionTag int tag, SessionCreationConfig creationConfig, SessionConfig config)1350         public IHintManager.SessionCreationReturn createHintSessionWithConfig(
1351                     @NonNull IBinder token, @SessionTag int tag,
1352                     SessionCreationConfig creationConfig, SessionConfig config) {
1353             if (!isHintSessionSupported()) {
1354                 throw new UnsupportedOperationException("PowerHintSessions are not supported!");
1355             }
1356 
1357             java.util.Objects.requireNonNull(token);
1358             java.util.Objects.requireNonNull(creationConfig.tids);
1359 
1360             final int[] tids = creationConfig.tids;
1361             Preconditions.checkArgument(tids.length != 0, "tids should"
1362                     + " not be empty.");
1363 
1364 
1365             final int callingUid = Binder.getCallingUid();
1366             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
1367             final long identity = Binder.clearCallingIdentity();
1368             final long durationNanos = creationConfig.targetWorkDurationNanos;
1369 
1370             boolean isGraphicsPipeline = false;
1371             boolean isAutoTimed = false;
1372             if (creationConfig.modesToEnable != null) {
1373                 for (int mode : creationConfig.modesToEnable) {
1374                     if (mode == SessionMode.GRAPHICS_PIPELINE) {
1375                         isGraphicsPipeline = true;
1376                     }
1377                     if (mode == SessionMode.AUTO_CPU || mode == SessionMode.AUTO_GPU) {
1378                         isAutoTimed = true;
1379                     }
1380                 }
1381             }
1382 
1383             if (isAutoTimed) {
1384                 Preconditions.checkArgument(isGraphicsPipeline,
1385                         "graphics pipeline mode not enabled for an automatically timed session");
1386             }
1387 
1388             try {
1389                 final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length)
1390                         : null;
1391                 final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids,
1392                         nonIsolated);
1393                 if (invalidTid != null) {
1394                     final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid);
1395                     Slogf.w(TAG, errMsg);
1396                     throw new SecurityException(errMsg);
1397                 }
1398                 if (resetOnForkEnabled()) {
1399                     try {
1400                         for (int tid : tids) {
1401                             int policy = Process.getThreadScheduler(tid);
1402                             // If the thread is not using the default scheduling policy (SCHED_OTHER),
1403                             // we don't change it.
1404                             if (policy != Process.SCHED_OTHER) {
1405                                 continue;
1406                             }
1407                             // set the SCHED_RESET_ON_FORK flag.
1408                             int prio = Process.getThreadPriority(tid);
1409                             Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
1410                             Process.setThreadPriority(tid, prio);
1411                         }
1412                     } catch (Exception e) {
1413                         Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
1414                                 + Arrays.toString(tids), e);
1415                     }
1416                 }
1417 
1418                 if (adpfSessionTag() && tag == SessionTag.APP) {
1419                     // If the category of the app is a game,
1420                     // we change the session tag to SessionTag.GAME
1421                     // as it was not previously classified
1422                     switch (getUidApplicationCategory(callingUid)) {
1423                         case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME;
1424                         case ApplicationInfo.CATEGORY_UNDEFINED ->
1425                             // We use CATEGORY_UNDEFINED to filter the case when
1426                             // PackageManager.NameNotFoundException is caught,
1427                             // which should not happen.
1428                             tag = SessionTag.APP;
1429                         default -> tag = SessionTag.APP;
1430                     }
1431                 }
1432                 config.id = -1;
1433                 Long halSessionPtr = null;
1434                 if (mConfigCreationSupport.get()) {
1435                     try {
1436                         halSessionPtr = mNativeWrapper.halCreateHintSessionWithConfig(
1437                                 callingTgid, callingUid, tids, durationNanos, tag, config);
1438                     } catch (UnsupportedOperationException e) {
1439                         mConfigCreationSupport.set(false);
1440                     } catch (IllegalStateException e) {
1441                         Slog.e("createHintSessionWithConfig failed: ", e.getMessage());
1442                         throw new IllegalStateException(
1443                             "createHintSessionWithConfig failed: " + e.getMessage());
1444                     }
1445                 }
1446 
1447                 if (halSessionPtr == null) {
1448                     try {
1449                         halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid,
1450                                 callingUid, tids, durationNanos);
1451                     } catch (UnsupportedOperationException e) {
1452                         Slog.w("createHintSession unsupported: ", e.getMessage());
1453                         throw new UnsupportedOperationException(
1454                             "createHintSession unsupported: " + e.getMessage());
1455                     } catch (IllegalStateException e) {
1456                         Slog.e("createHintSession failed: ", e.getMessage());
1457                         throw new IllegalStateException(
1458                             "createHintSession failed: " + e.getMessage());
1459                     }
1460                 }
1461 
1462                 if (powerhintThreadCleanup()) {
1463                     synchronized (mNonIsolatedTidsLock) {
1464                         for (int i = nonIsolated.size() - 1; i >= 0; i--) {
1465                             mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), new ArraySet<>());
1466                             mNonIsolatedTids.get(nonIsolated.get(i)).add(halSessionPtr);
1467                         }
1468                     }
1469                 }
1470 
1471                 final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr;
1472                 logPerformanceHintSessionAtom(
1473                         callingUid, sessionIdForTracing, durationNanos, tids, tag);
1474 
1475                 synchronized (mSessionSnapshotMapLock) {
1476                     // Update session snapshot upon session creation
1477                     mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>())
1478                             .computeIfAbsent(tag, k -> new AppHintSessionSnapshot())
1479                             .updateUponSessionCreation(tids.length, durationNanos);
1480                 }
1481                 AppHintSession hs = null;
1482                 synchronized (mLock) {
1483                     Integer configId = null;
1484                     if (config.id != -1) {
1485                         configId = new Integer((int) config.id);
1486                     }
1487                     hs = new AppHintSession(callingUid, callingTgid, tag, tids,
1488                             token, halSessionPtr, durationNanos, configId);
1489                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
1490                             mActiveSessions.get(callingUid);
1491                     if (tokenMap == null) {
1492                         tokenMap = new ArrayMap<>(1);
1493                         mActiveSessions.put(callingUid, tokenMap);
1494                     }
1495                     ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
1496                     if (sessionSet == null) {
1497                         sessionSet = new ArraySet<>(1);
1498                         tokenMap.put(token, sessionSet);
1499                     }
1500                     sessionSet.add(hs);
1501                     mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
1502                 }
1503 
1504                 if (hs != null) {
1505                     if (creationConfig.modesToEnable != null) {
1506                         for (int sessionMode : creationConfig.modesToEnable) {
1507                             hs.setMode(sessionMode, true);
1508                         }
1509                     }
1510 
1511                     if (creationConfig.layerTokens != null
1512                             && creationConfig.layerTokens.length > 0) {
1513                         hs.associateToLayers(creationConfig.layerTokens);
1514                     }
1515 
1516                     synchronized (mThreadsUsageObject) {
1517                         mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>());
1518                         ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
1519                         for (int i = 0; i < tids.length; ++i) {
1520                             threadsSet.add(new ThreadUsageTracker(tids[i], isGraphicsPipeline));
1521                         }
1522                     }
1523                 }
1524 
1525                 IHintManager.SessionCreationReturn out = new IHintManager.SessionCreationReturn();
1526                 out.pipelineThreadLimitExceeded = tooManyPipelineThreads(callingUid);
1527                 out.session = hs;
1528                 return out;
1529             } finally {
1530                 Binder.restoreCallingIdentity(identity);
1531             }
1532         }
1533 
1534         @Override
getSessionChannel(IBinder token)1535         public @Nullable ChannelConfig getSessionChannel(IBinder token) {
1536             if (mPowerHalVersion < 5 || !adpfUseFmqChannel()
1537                     || mFMQUsesIntegratedEventFlag) {
1538                 return null;
1539             }
1540             java.util.Objects.requireNonNull(token);
1541             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
1542             final int callingUid = Binder.getCallingUid();
1543             ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token);
1544             // FMQ V1 requires a separate event flag to be passed, and the default no-op
1545             // implmenentation in PowerHAL does not return such a shared flag. This helps
1546             // avoid using the FMQ on a default impl that does not support it.
1547             if (item.getConfig().eventFlagDescriptor == null) {
1548                 mFMQUsesIntegratedEventFlag = true;
1549                 closeSessionChannel();
1550                 return null;
1551             }
1552             return item.getConfig();
1553         };
1554 
1555         @Override
closeSessionChannel()1556         public void closeSessionChannel() {
1557             if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
1558                 return;
1559             }
1560             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
1561             final int callingUid = Binder.getCallingUid();
1562             removeChannelItem(callingTgid, callingUid);
1563         };
1564 
getMaxGraphicsPipelineThreadsCount()1565         public int getMaxGraphicsPipelineThreadsCount() {
1566             return MAX_GRAPHICS_PIPELINE_THREADS_COUNT;
1567         }
1568 
1569         @Override
setHintSessionThreads(@onNull IHintSession hintSession, @NonNull int[] tids)1570         public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) {
1571             AppHintSession appHintSession = (AppHintSession) hintSession;
1572             appHintSession.setThreads(tids);
1573         }
1574 
1575         @Override
getHintSessionThreadIds(@onNull IHintSession hintSession)1576         public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) {
1577             AppHintSession appHintSession = (AppHintSession) hintSession;
1578             return appHintSession.getThreadIds();
1579         }
1580 
1581         @Override
getCpuHeadroom(@onNull CpuHeadroomParamsInternal params)1582         public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
1583             if (!mSupportInfo.headroom.isCpuSupported) {
1584                 throw new UnsupportedOperationException();
1585             }
1586             checkCpuHeadroomParams(params);
1587             final int uid = Binder.getCallingUid();
1588             final int pid = Binder.getCallingPid();
1589             final CpuHeadroomParams halParams = new CpuHeadroomParams();
1590             halParams.tids = new int[]{pid};
1591             halParams.calculationType = params.calculationType;
1592             halParams.calculationWindowMillis = params.calculationWindowMillis;
1593             if (params.usesDeviceHeadroom) {
1594                 halParams.tids = new int[]{};
1595             } else if (params.tids != null && params.tids.length > 0) {
1596                 if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && mCheckHeadroomTid) {
1597                     final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
1598                     for (int tid : params.tids) {
1599                         if (Process.getThreadGroupLeader(tid) != tgid) {
1600                             throw new SecurityException("TID " + tid
1601                                     + " doesn't belong to the calling process with pid "
1602                                     + tgid);
1603                         }
1604                     }
1605                 }
1606                 if (mCheckHeadroomAffinity && params.tids.length > 1) {
1607                     checkThreadAffinityForTids(params.tids);
1608                 }
1609                 halParams.tids = params.tids;
1610             }
1611             synchronized (mCpuHeadroomLock) {
1612                 final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams);
1613                 if (res != null) return res;
1614             }
1615             final boolean shouldCheckUserModeCpuTime =
1616                     mEnforceCpuHeadroomUserModeCpuTimeCheck
1617                             || (UserHandle.getAppId(uid) != Process.SYSTEM_UID
1618                             && mContext.checkCallingPermission(
1619                             Manifest.permission.DEVICE_POWER)
1620                             == PackageManager.PERMISSION_DENIED);
1621 
1622             if (shouldCheckUserModeCpuTime) {
1623                 synchronized (mCpuHeadroomLock) {
1624                     if (!checkPerUidUserModeCpuTimeElapsedLocked(uid)) {
1625                         return null;
1626                     }
1627                 }
1628             }
1629             // return from HAL directly
1630             try {
1631                 final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
1632                 if (result == null) {
1633                     Slog.wtf(TAG, "CPU headroom from Power HAL is invalid");
1634                     return null;
1635                 }
1636                 synchronized (mCpuHeadroomLock) {
1637                     mCpuHeadroomCache.add(halParams, result);
1638                 }
1639                 if (shouldCheckUserModeCpuTime) {
1640                     synchronized (mCpuHeadroomLock) {
1641                         mUidToLastUserModeJiffies.put(uid, mLastCpuUserModeJiffies);
1642                     }
1643                 }
1644                 return result;
1645             } catch (RemoteException e) {
1646                 Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
1647                 return null;
1648             }
1649         }
checkThreadAffinityForTids(int[] tids)1650         private void checkThreadAffinityForTids(int[] tids) {
1651             long[] reference = null;
1652             for (int tid : tids) {
1653                 long[] affinity;
1654                 try {
1655                     affinity = Process.getSchedAffinity(tid);
1656                 } catch (Exception e) {
1657                     Slog.e(TAG, "Failed to get affinity " + tid, e);
1658                     throw new IllegalStateException("Could not check affinity for tid " + tid);
1659                 }
1660                 if (reference == null) {
1661                     reference = affinity;
1662                 } else if (!Arrays.equals(reference, affinity)) {
1663                     Slog.d(TAG, "Thread affinity is different: tid "
1664                             + tids[0] + "->" + Arrays.toString(reference) + ", tid "
1665                             + tid + "->" + Arrays.toString(affinity));
1666                     throw new IllegalStateException("Thread affinity is not the same for tids "
1667                             + Arrays.toString(tids));
1668                 }
1669             }
1670         }
1671 
1672         // check if there has been sufficient user mode cpu time elapsed since last call
1673         // from the same uid
1674         @GuardedBy("mCpuHeadroomLock")
checkPerUidUserModeCpuTimeElapsedLocked(int uid)1675         private boolean checkPerUidUserModeCpuTimeElapsedLocked(int uid) {
1676             // skip checking proc stat if it's within mCheckHeadroomProcStatMinMillis
1677             if (System.currentTimeMillis() - mLastCpuUserModeTimeCheckedMillis
1678                     > mCheckHeadroomProcStatMinMillis) {
1679                 try {
1680                     mLastCpuUserModeJiffies = getUserModeJiffies();
1681                 } catch (Exception e) {
1682                     Slog.e(TAG, "Failed to get user mode CPU time", e);
1683                     return false;
1684                 }
1685                 mLastCpuUserModeTimeCheckedMillis = System.currentTimeMillis();
1686             }
1687             if (mUidToLastUserModeJiffies.containsKey(uid)) {
1688                 long uidLastUserModeJiffies = mUidToLastUserModeJiffies.get(uid);
1689                 if ((mLastCpuUserModeJiffies - uidLastUserModeJiffies) * mJiffyMillis
1690                         < mSupportInfo.headroom.cpuMinIntervalMillis) {
1691                     Slog.w(TAG, "UID " + uid + " is requesting CPU headroom too soon");
1692                     Slog.d(TAG, "UID " + uid + " last request at "
1693                             + uidLastUserModeJiffies * mJiffyMillis
1694                             + "ms with device currently at "
1695                             + mLastCpuUserModeJiffies * mJiffyMillis
1696                             + "ms, the interval: "
1697                             + (mLastCpuUserModeJiffies - uidLastUserModeJiffies)
1698                             * mJiffyMillis + "ms is less than require minimum interval "
1699                             + mSupportInfo.headroom.cpuMinIntervalMillis + "ms");
1700                     return false;
1701                 }
1702             }
1703             return true;
1704         }
1705 
checkCpuHeadroomParams(CpuHeadroomParamsInternal params)1706         private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) {
1707             boolean calculationTypeMatched = false;
1708             try {
1709                 for (final Field field :
1710                         CpuHeadroomParams.CalculationType.class.getDeclaredFields()) {
1711                     if (field.getType() == byte.class) {
1712                         byte value = field.getByte(null);
1713                         if (value == params.calculationType) {
1714                             calculationTypeMatched = true;
1715                             break;
1716                         }
1717                     }
1718                 }
1719             } catch (IllegalAccessException e) {
1720                 Slog.wtf(TAG, "Checking the calculation type was unexpectedly not allowed");
1721             }
1722             if (!calculationTypeMatched) {
1723                 throw new IllegalArgumentException(
1724                         "Unknown CPU headroom calculation type " + (int) params.calculationType);
1725             }
1726             if (params.calculationWindowMillis < mSupportInfo.headroom.cpuMinCalculationWindowMillis
1727                     || params.calculationWindowMillis
1728                     > mSupportInfo.headroom.cpuMaxCalculationWindowMillis) {
1729                 throw new IllegalArgumentException(
1730                         "Invalid CPU headroom calculation window, expected ["
1731                                 + mSupportInfo.headroom.cpuMinCalculationWindowMillis
1732                                 + ", "
1733                                 + mSupportInfo.headroom.cpuMaxCalculationWindowMillis
1734                                 + "] but got "
1735                                 + params.calculationWindowMillis);
1736             }
1737             if (!params.usesDeviceHeadroom) {
1738                 if (params.tids != null && params.tids.length > mCpuHeadroomMaxTidCnt) {
1739                     throw new IllegalArgumentException(
1740                             "More than " + mCpuHeadroomMaxTidCnt + " TIDs requested: "
1741                                     + params.tids.length);
1742                 }
1743             }
1744         }
1745 
1746         @Override
getGpuHeadroom(@onNull GpuHeadroomParamsInternal params)1747         public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
1748             if (!mSupportInfo.headroom.isGpuSupported) {
1749                 throw new UnsupportedOperationException();
1750             }
1751             checkGpuHeadroomParams(params);
1752             final GpuHeadroomParams halParams = new GpuHeadroomParams();
1753             halParams.calculationType = params.calculationType;
1754             halParams.calculationWindowMillis = params.calculationWindowMillis;
1755             synchronized (mGpuHeadroomLock) {
1756                 final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams);
1757                 if (res != null) return res;
1758             }
1759             // return from HAL directly
1760             try {
1761                 final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams);
1762                 if (headroom == null) {
1763                     Slog.wtf(TAG, "GPU headroom from Power HAL is invalid");
1764                     return null;
1765                 }
1766                 synchronized (mGpuHeadroomLock) {
1767                     mGpuHeadroomCache.add(halParams, headroom);
1768                 }
1769                 return headroom;
1770             } catch (RemoteException e) {
1771                 Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e);
1772                 return null;
1773             }
1774         }
1775 
checkGpuHeadroomParams(GpuHeadroomParamsInternal params)1776         private void checkGpuHeadroomParams(GpuHeadroomParamsInternal params) {
1777             boolean calculationTypeMatched = false;
1778             try {
1779                 for (final Field field :
1780                         GpuHeadroomParams.CalculationType.class.getDeclaredFields()) {
1781                     if (field.getType() == byte.class) {
1782                         byte value = field.getByte(null);
1783                         if (value == params.calculationType) {
1784                             calculationTypeMatched = true;
1785                             break;
1786                         }
1787                     }
1788                 }
1789             } catch (IllegalAccessException e) {
1790                 Slog.wtf(TAG, "Checking the calculation type was unexpectedly not allowed");
1791             }
1792             if (!calculationTypeMatched) {
1793                 throw new IllegalArgumentException(
1794                         "Unknown GPU headroom calculation type " + (int) params.calculationType);
1795             }
1796             if (params.calculationWindowMillis < mSupportInfo.headroom.gpuMinCalculationWindowMillis
1797                     || params.calculationWindowMillis
1798                     > mSupportInfo.headroom.gpuMaxCalculationWindowMillis) {
1799                 throw new IllegalArgumentException(
1800                         "Invalid GPU headroom calculation window, expected ["
1801                                 + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", "
1802                                 + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "] but got "
1803                                 + params.calculationWindowMillis);
1804             }
1805         }
1806 
1807         @Override
getCpuHeadroomMinIntervalMillis()1808         public long getCpuHeadroomMinIntervalMillis() {
1809             if (!mSupportInfo.headroom.isCpuSupported) {
1810                 throw new UnsupportedOperationException();
1811             }
1812             return mSupportInfo.headroom.cpuMinIntervalMillis;
1813         }
1814 
1815         @Override
getGpuHeadroomMinIntervalMillis()1816         public long getGpuHeadroomMinIntervalMillis() {
1817             if (!mSupportInfo.headroom.isGpuSupported) {
1818                 throw new UnsupportedOperationException();
1819             }
1820             return mSupportInfo.headroom.gpuMinIntervalMillis;
1821         }
1822 
1823         @Override
passSessionManagerBinder(IBinder sessionManager)1824         public void passSessionManagerBinder(IBinder sessionManager) {
1825             // Ensure caller is internal
1826             if (Process.myUid() != Binder.getCallingUid()) {
1827                 return;
1828             }
1829             mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
1830         }
1831 
1832         @Override
1833         public IHintManager.HintManagerClientData
registerClient(@onNull IHintManager.IHintManagerClient clientBinder)1834                 registerClient(@NonNull IHintManager.IHintManagerClient clientBinder) {
1835             return getClientData();
1836         }
1837 
1838         @Override
getClientData()1839         public IHintManager.HintManagerClientData getClientData() {
1840             IHintManager.HintManagerClientData out = new IHintManager.HintManagerClientData();
1841             out.preferredRateNanos = mHintSessionPreferredRate;
1842             out.maxGraphicsPipelineThreads = getMaxGraphicsPipelineThreadsCount();
1843             out.maxCpuHeadroomThreads = DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT;
1844             out.powerHalVersion = mPowerHalVersion;
1845             out.supportInfo = mSupportInfo;
1846             return out;
1847         }
1848 
1849         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1850         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1851             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
1852                 return;
1853             }
1854             pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
1855             pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT);
1856             pw.println("Hint Session Support: " + isHintSessionSupported());
1857             pw.println("Active Sessions:");
1858             synchronized (mLock) {
1859                 for (int i = 0; i < mActiveSessions.size(); i++) {
1860                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
1861                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
1862                             mActiveSessions.valueAt(i);
1863                     for (int j = 0; j < tokenMap.size(); j++) {
1864                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
1865                         for (int k = 0; k < sessionSet.size(); ++k) {
1866                             pw.println("  Session:");
1867                             sessionSet.valueAt(k).dump(pw, "    ");
1868                         }
1869                     }
1870                 }
1871             }
1872             pw.println("CPU Headroom Supported: " + mSupportInfo.headroom.isCpuSupported);
1873             if (mSupportInfo.headroom.isCpuSupported) {
1874                 pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis);
1875                 pw.println("CPU Headroom TID Max Count: " + mCpuHeadroomMaxTidCnt);
1876                 pw.println("CPU Headroom TID Max Count From HAL: "
1877                         + mSupportInfo.headroom.cpuMaxTidCount);
1878                 pw.println("CPU Headroom Calculation Window Range: ["
1879                         + mSupportInfo.headroom.cpuMinCalculationWindowMillis + ", "
1880                         + mSupportInfo.headroom.cpuMaxCalculationWindowMillis + "]");
1881                 try {
1882                     CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
1883                     params.usesDeviceHeadroom = true;
1884                     CpuHeadroomResult ret = getCpuHeadroom(params);
1885                     pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
1886                 } catch (Exception e) {
1887                     Slog.d(TAG, "Failed to dump CPU headroom", e);
1888                     pw.println("CPU headroom: N/A");
1889                 }
1890             }
1891             pw.println("GPU Headroom Supported: " + mSupportInfo.headroom.isGpuSupported);
1892             if (mSupportInfo.headroom.isGpuSupported) {
1893                 pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis);
1894                 pw.println("GPU Headroom Calculation Window Range: ["
1895                         + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", "
1896                         + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "]");
1897                 try {
1898                     GpuHeadroomParamsInternal params = new GpuHeadroomParamsInternal();
1899                     params.calculationWindowMillis = mDefaultGpuHeadroomCalculationWindowMillis;
1900                     GpuHeadroomResult ret = getGpuHeadroom(params);
1901                     pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
1902                 } catch (Exception e) {
1903                     Slog.d(TAG, "Failed to dump GPU headroom", e);
1904                     pw.println("GPU headroom: N/A");
1905                 }
1906             }
1907         }
1908 
getUserModeJiffies()1909         private long getUserModeJiffies() throws IOException {
1910             String filePath =
1911                     mProcStatFilePathOverride == null ? "/proc/stat" : mProcStatFilePathOverride;
1912             try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
1913                 String line;
1914                 while ((line = reader.readLine()) != null) {
1915                     Matcher matcher = PROC_STAT_CPU_TIME_TOTAL_PATTERN.matcher(line.trim());
1916                     if (matcher.find()) {
1917                         long userJiffies = Long.parseLong(matcher.group("user"));
1918                         long niceJiffies = Long.parseLong(matcher.group("nice"));
1919                         Slog.d(TAG,
1920                                 "user: " + userJiffies + " nice: " + niceJiffies
1921                                         + " total " + (userJiffies + niceJiffies));
1922                         reader.close();
1923                         return userJiffies + niceJiffies;
1924                     }
1925                 }
1926             }
1927             throw new IllegalStateException("Can't find cpu line in " + filePath);
1928         }
1929 
logPerformanceHintSessionAtom(int uid, long sessionId, long targetDuration, int[] tids, @SessionTag int sessionTag)1930         private void logPerformanceHintSessionAtom(int uid, long sessionId,
1931                 long targetDuration, int[] tids, @SessionTag int sessionTag) {
1932             FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid,
1933                     sessionId, targetDuration, tids.length, sessionTag);
1934         }
1935 
getUidApplicationCategory(int uid)1936         private int getUidApplicationCategory(int uid) {
1937             try {
1938                 final String packageName = mPackageManager.getNameForUid(uid);
1939                 final ApplicationInfo applicationInfo =
1940                         mPackageManager.getApplicationInfo(packageName, PackageManager.MATCH_ALL);
1941                 return applicationInfo.category;
1942             } catch (PackageManager.NameNotFoundException e) {
1943                 return ApplicationInfo.CATEGORY_UNDEFINED;
1944             }
1945         }
1946     }
1947 
1948     @VisibleForTesting
1949     final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
1950         protected final int mUid;
1951         protected final int mPid;
1952         protected final int mTag;
1953         protected int[] mThreadIds;
1954         protected final IBinder mToken;
1955         protected long mHalSessionPtr;
1956         protected long mTargetDurationNanos;
1957         protected boolean mUpdateAllowedByProcState;
1958         protected int[] mNewThreadIds;
1959         protected boolean mPowerEfficient;
1960         protected boolean mGraphicsPipeline;
1961         protected boolean mHasBeenPowerEfficient;
1962         protected boolean mHasBeenGraphicsPipeline;
1963         protected boolean mShouldForcePause;
1964         protected Integer mSessionId;
1965         protected boolean mTrackedBySF;
1966 
AppHintSession( int uid, int pid, int sessionTag, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos, Integer sessionId)1967         protected AppHintSession(
1968                 int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
1969                 long halSessionPtr, long durationNanos, Integer sessionId) {
1970             mUid = uid;
1971             mPid = pid;
1972             mTag = sessionTag;
1973             mToken = token;
1974             mThreadIds = threadIds;
1975             mHalSessionPtr = halSessionPtr;
1976             mTargetDurationNanos = durationNanos;
1977             mUpdateAllowedByProcState = true;
1978             mPowerEfficient = false;
1979             mGraphicsPipeline = false;
1980             mHasBeenPowerEfficient = false;
1981             mHasBeenGraphicsPipeline = false;
1982             mShouldForcePause = false;
1983             mSessionId = sessionId;
1984             mTrackedBySF = false;
1985             final boolean allowed = mUidObserver.isUidForeground(mUid);
1986             updateHintAllowedByProcState(allowed);
1987             try {
1988                 token.linkToDeath(this, 0);
1989             } catch (RemoteException e) {
1990                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
1991                 throw new IllegalStateException("Client already dead", e);
1992             }
1993         }
1994 
1995         @VisibleForTesting
updateHintAllowedByProcState(boolean allowed)1996         boolean updateHintAllowedByProcState(boolean allowed) {
1997             synchronized (this) {
1998                 if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) {
1999                     resume();
2000                 }
2001                 if (!allowed && mUpdateAllowedByProcState) {
2002                     pause();
2003                 }
2004                 mUpdateAllowedByProcState = allowed;
2005                 return mUpdateAllowedByProcState;
2006             }
2007         }
2008 
isHintAllowed()2009         boolean isHintAllowed() {
2010             return mHalSessionPtr != 0 && mUpdateAllowedByProcState && !mShouldForcePause;
2011         }
2012 
2013         @Override
updateTargetWorkDuration(long targetDurationNanos)2014         public void updateTargetWorkDuration(long targetDurationNanos) {
2015             synchronized (this) {
2016                 if (!isHintAllowed()) {
2017                     return;
2018                 }
2019                 Preconditions.checkArgument(targetDurationNanos >= 0, "Expected"
2020                         + " the target duration to be greater than or equal to 0.");
2021                 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
2022                 mTargetDurationNanos = targetDurationNanos;
2023             }
2024             synchronized (mSessionSnapshotMapLock) {
2025                 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
2026                         mSessionSnapshotMap.get(mUid);
2027                 if (sessionSnapshots == null) {
2028                     Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
2029                     return;
2030                 }
2031                 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
2032                 if (sessionSnapshot == null) {
2033                     Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
2034                     return;
2035                 }
2036                 sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos);
2037             }
2038         }
2039 
2040         @Override
reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos)2041         public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
2042             synchronized (this) {
2043                 if (!isHintAllowed()) {
2044                     return;
2045                 }
2046                 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
2047                         + " of hint durations shouldn't be 0.");
2048                 Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length,
2049                         "The length of durations and timestamps should be the same.");
2050                 for (int i = 0; i < actualDurationNanos.length; i++) {
2051                     if (actualDurationNanos[i] <= 0) {
2052                         throw new IllegalArgumentException(
2053                                 String.format("durations[%d]=%d should be greater than 0",
2054                                         i, actualDurationNanos[i]));
2055                     }
2056                 }
2057                 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos,
2058                         timeStampNanos);
2059             }
2060         }
2061 
2062         /** TODO: consider monitor session threads and close session if any thread is dead. */
2063         @Override
close()2064         public void close() {
2065             synchronized (this) {
2066                 if (mHalSessionPtr == 0) return;
2067                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
2068                 mHalSessionPtr = 0;
2069                 try {
2070                     mToken.unlinkToDeath(this, 0);
2071                 } catch (NoSuchElementException ignored) {
2072                     Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
2073                 }
2074                 if (mTrackedBySF) {
2075                     if (mSessionManager != null) {
2076                         try {
2077                             mSessionManager.trackedSessionsDied(new int[]{mSessionId});
2078                         } catch (RemoteException e) {
2079                             throw new IllegalStateException(
2080                                     "Could not communicate with SessionManager", e);
2081                         }
2082                         mTrackedBySF = false;
2083                     } else {
2084                         Slog.e(TAG, "SessionManager is null but there are tracked sessions");
2085                     }
2086                 }
2087             }
2088             synchronized (mLock) {
2089                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
2090                 if (tokenMap == null) {
2091                     Slogf.w(TAG, "UID %d is not present in active session map", mUid);
2092                     return;
2093                 }
2094                 ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
2095                 if (sessionSet == null) {
2096                     Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
2097                     return;
2098                 }
2099                 sessionSet.remove(this);
2100                 if (sessionSet.isEmpty()) tokenMap.remove(mToken);
2101                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
2102             }
2103             synchronized (mSessionSnapshotMapLock) {
2104                 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
2105                         mSessionSnapshotMap.get(mUid);
2106                 if (sessionSnapshots == null) {
2107                     Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
2108                     return;
2109                 }
2110                 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
2111                 if (sessionSnapshot == null) {
2112                     Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
2113                     return;
2114                 }
2115                 sessionSnapshot.updateUponSessionClose();
2116             }
2117 
2118             if (mGraphicsPipeline) {
2119                 synchronized (mThreadsUsageObject) {
2120                     ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(mUid);
2121                     if (threadsSet == null) {
2122                         Slogf.w(TAG, "Threads Set is null for uid " + mUid);
2123                         return;
2124                     }
2125                     // remove all tids associated with this session
2126                     for (int i = 0; i < threadsSet.size(); ++i) {
2127                         if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) {
2128                             threadsSet.removeAt(i);
2129                         }
2130                     }
2131                     if (threadsSet.isEmpty()) {
2132                         mThreadsUsageMap.remove(mUid);
2133                     }
2134                 }
2135             }
2136             if (powerhintThreadCleanup()) {
2137                 synchronized (mNonIsolatedTidsLock) {
2138                     final int[] tids = getTidsInternal();
2139                     for (int tid : tids) {
2140                         if (mNonIsolatedTids.containsKey(tid)) {
2141                             mNonIsolatedTids.get(tid).remove(mHalSessionPtr);
2142                             if (mNonIsolatedTids.get(tid).isEmpty()) {
2143                                 mNonIsolatedTids.remove(tid);
2144                             }
2145                         }
2146                     }
2147                 }
2148             }
2149         }
2150 
2151         @Override
sendHint(@erformanceHintManager.Session.Hint int hint)2152         public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
2153             synchronized (this) {
2154                 if (!isHintAllowed()) {
2155                     return;
2156                 }
2157                 Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
2158                         + " greater than zero.");
2159                 mNativeWrapper.halSendHint(mHalSessionPtr, hint);
2160             }
2161         }
2162 
2163         @Override
associateToLayers(IBinder[] layerTokens)2164         public void associateToLayers(IBinder[] layerTokens) {
2165             synchronized (this) {
2166                 if (mSessionManager != null && mSessionId != null && layerTokens != null) {
2167                     // Sf only untracks a session when it dies
2168                     if (layerTokens.length > 0) {
2169                         mTrackedBySF = true;
2170                     }
2171                     try {
2172                         mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens);
2173                     } catch (RemoteException e) {
2174                         throw new IllegalStateException(
2175                                 "Could not communicate with SessionManager", e);
2176                     }
2177                 }
2178             }
2179         }
2180 
setThreads(@onNull int[] tids)2181         public void setThreads(@NonNull int[] tids) {
2182             setThreadsInternal(tids, true);
2183             if (tooManyPipelineThreads(Binder.getCallingUid())) {
2184                 // This is technically a success but we are going to throw a fit anyway
2185                 throw new ServiceSpecificException(5,
2186                                     "Not enough available graphics pipeline threads.");
2187             }
2188         }
2189 
setThreadsInternal(int[] tids, boolean checkTid)2190         private void setThreadsInternal(int[] tids, boolean checkTid) {
2191             if (tids.length == 0) {
2192                 throw new IllegalArgumentException("Thread id list can't be empty.");
2193             }
2194 
2195             final int callingUid = Binder.getCallingUid();
2196 
2197             synchronized (this) {
2198                 if (mHalSessionPtr == 0) {
2199                     return;
2200                 }
2201                 if (!mUpdateAllowedByProcState) {
2202                     Slogf.v(TAG, "update hint not allowed, storing tids.");
2203                     mNewThreadIds = tids;
2204                     mShouldForcePause = false;
2205                     return;
2206                 }
2207                 if (checkTid) {
2208                     final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
2209                     final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null;
2210                     final long identity = Binder.clearCallingIdentity();
2211                     try {
2212                         final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids,
2213                                 nonIsolated);
2214                         if (invalidTid != null) {
2215                             final String errMsg = formatTidCheckErrMsg(callingUid, tids,
2216                                     invalidTid);
2217                             Slogf.w(TAG, errMsg);
2218                             throw new SecurityException(errMsg);
2219                         }
2220                         if (resetOnForkEnabled()) {
2221                             try {
2222                                 for (int tid : tids) {
2223                                     int policy = Process.getThreadScheduler(tid);
2224                                     // If the thread is not using the default scheduling policy (SCHED_OTHER),
2225                                     // we don't change it.
2226                                     if (policy != Process.SCHED_OTHER) {
2227                                         continue;
2228                                     }
2229                                     // set the SCHED_RESET_ON_FORK flag.
2230                                     int prio = Process.getThreadPriority(tid);
2231                                     Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0);
2232                                     Process.setThreadPriority(tid, prio);
2233                                 }
2234                             } catch (Exception e) {
2235                                 Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids "
2236                                         + Arrays.toString(tids), e);
2237                             }
2238                         }
2239                         if (powerhintThreadCleanup()) {
2240                             synchronized (mNonIsolatedTidsLock) {
2241                                 for (int i = nonIsolated.size() - 1; i >= 0; i--) {
2242                                     mNonIsolatedTids.putIfAbsent(nonIsolated.get(i),
2243                                             new ArraySet<>());
2244                                     mNonIsolatedTids.get(nonIsolated.get(i)).add(mHalSessionPtr);
2245                                 }
2246                             }
2247                         }
2248                     } finally {
2249                         Binder.restoreCallingIdentity(identity);
2250                     }
2251                 }
2252                 mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
2253 
2254                 synchronized (mThreadsUsageObject) {
2255                     // replace old tids with new ones
2256                     ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
2257                     if (threadsSet == null) {
2258                         mThreadsUsageMap.put(callingUid, new ArraySet<ThreadUsageTracker>());
2259                         threadsSet = mThreadsUsageMap.get(callingUid);
2260                     }
2261                     for (int i = 0; i < threadsSet.size(); ++i) {
2262                         if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) {
2263                             threadsSet.removeAt(i);
2264                         }
2265                     }
2266                     for (int tid : tids) {
2267                         threadsSet.add(new ThreadUsageTracker(tid, mGraphicsPipeline));
2268                     }
2269                 }
2270                 mThreadIds = tids;
2271                 mNewThreadIds = null;
2272                 // if the update is allowed but the session is force paused by tid clean up, then
2273                 // it's waiting for this tid update to resume
2274                 if (mShouldForcePause) {
2275                     resume();
2276                     mShouldForcePause = false;
2277                 }
2278             }
2279             synchronized (mSessionSnapshotMapLock) {
2280                 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
2281                         mSessionSnapshotMap.get(mUid);
2282                 if (sessionSnapshots == null) {
2283                     Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
2284                     return;
2285                 }
2286                 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
2287                 if (sessionSnapshot == null) {
2288                     Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag "
2289                             + mTag);
2290                     return;
2291                 }
2292                 sessionSnapshot.updateThreadCount(tids.length);
2293             }
2294         }
2295 
getThreadIds()2296         public int[] getThreadIds() {
2297             synchronized (this) {
2298                 return Arrays.copyOf(mThreadIds, mThreadIds.length);
2299             }
2300         }
2301 
2302         @VisibleForTesting
getTidsInternal()2303         int[] getTidsInternal() {
2304             synchronized (this) {
2305                 return mNewThreadIds != null ? Arrays.copyOf(mNewThreadIds, mNewThreadIds.length)
2306                         : Arrays.copyOf(mThreadIds, mThreadIds.length);
2307             }
2308         }
2309 
isClosed()2310         boolean isClosed() {
2311             synchronized (this) {
2312                 return mHalSessionPtr == 0;
2313             }
2314         }
2315 
isForcePaused()2316         boolean isForcePaused() {
2317             synchronized (this) {
2318                 return mShouldForcePause;
2319             }
2320         }
2321         @Override
setMode(int mode, boolean enabled)2322         public void setMode(int mode, boolean enabled) {
2323             synchronized (this) {
2324                 if (!isHintAllowed()) {
2325                     return;
2326                 }
2327                 Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
2328                         + " greater than zero.");
2329                 if (mode == SessionMode.POWER_EFFICIENCY) {
2330                     mPowerEfficient = enabled;
2331                 } else if (mode == SessionMode.GRAPHICS_PIPELINE) {
2332                     mGraphicsPipeline = enabled;
2333                 }
2334                 mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
2335             }
2336             if (enabled) {
2337                 if (mode == SessionMode.POWER_EFFICIENCY) {
2338                     if (!mHasBeenPowerEfficient) {
2339                         mHasBeenPowerEfficient = true;
2340                         synchronized (mSessionSnapshotMapLock) {
2341                             ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
2342                                     mSessionSnapshotMap.get(mUid);
2343                             if (sessionSnapshots == null) {
2344                                 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
2345                                 return;
2346                             }
2347                             AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
2348                             if (sessionSnapshot == null) {
2349                                 Slogf.w(TAG, "Session snapshot is null for uid " + mUid
2350                                         + " and tag " + mTag);
2351                                 return;
2352                             }
2353                             sessionSnapshot.logPowerEfficientSession();
2354                         }
2355                     }
2356                 } else if (mode == SessionMode.GRAPHICS_PIPELINE) {
2357                     if (!mHasBeenGraphicsPipeline) {
2358                         mHasBeenGraphicsPipeline = true;
2359                         synchronized (mSessionSnapshotMapLock) {
2360                             ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
2361                                     mSessionSnapshotMap.get(mUid);
2362                             if (sessionSnapshots == null) {
2363                                 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
2364                                 return;
2365                             }
2366                             AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
2367                             if (sessionSnapshot == null) {
2368                                 Slogf.w(TAG, "Session snapshot is null for uid " + mUid
2369                                         + " and tag " + mTag);
2370                                 return;
2371                             }
2372                             sessionSnapshot.logGraphicsPipelineSession();
2373                         }
2374                     }
2375                 }
2376             }
2377         }
2378 
2379         @Override
reportActualWorkDuration2(WorkDuration[] workDurations)2380         public void reportActualWorkDuration2(WorkDuration[] workDurations) {
2381             synchronized (this) {
2382                 if (!isHintAllowed()) {
2383                     return;
2384                 }
2385                 Preconditions.checkArgument(workDurations.length != 0, "the count"
2386                         + " of work durations shouldn't be 0.");
2387                 for (int i = 0; i < workDurations.length; i++) {
2388                     validateWorkDuration(workDurations[i]);
2389                 }
2390                 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
2391             }
2392         }
2393 
isPowerEfficient()2394         public boolean isPowerEfficient() {
2395             synchronized (this) {
2396                 return mPowerEfficient;
2397             }
2398         }
2399 
isGraphicsPipeline()2400         public boolean isGraphicsPipeline() {
2401             synchronized (this) {
2402                 return mGraphicsPipeline;
2403             }
2404         }
2405 
getUid()2406         public int getUid() {
2407             return mUid;
2408         }
2409 
isTrackedBySf()2410         public boolean isTrackedBySf() {
2411             synchronized (this) {
2412                 return mTrackedBySF;
2413             }
2414         }
2415 
setTrackedBySf(boolean tracked)2416         public void setTrackedBySf(boolean tracked) {
2417             synchronized (this) {
2418                 mTrackedBySF = tracked;
2419             }
2420         }
2421 
2422 
getTag()2423         public int getTag() {
2424             return mTag;
2425         }
2426 
getSessionId()2427         public Integer getSessionId() {
2428             return mSessionId;
2429         }
2430 
getTargetDurationNs()2431         public long getTargetDurationNs() {
2432             synchronized (this) {
2433                 return mTargetDurationNanos;
2434             }
2435         }
2436 
validateWorkDuration(WorkDuration workDuration)2437         void validateWorkDuration(WorkDuration workDuration) {
2438             if (DEBUG) {
2439                 Slogf.d(TAG, "WorkDuration("
2440                         + workDuration.durationNanos + ", "
2441                         + workDuration.workPeriodStartTimestampNanos + ", "
2442                         + workDuration.cpuDurationNanos + ", "
2443                         + workDuration.gpuDurationNanos + ")");
2444             }
2445 
2446             // Allow work period start timestamp to be zero in system server side because
2447             // legacy API call will use zero value. It can not be estimated with the timestamp
2448             // the sample is received because the samples could stack up.
2449             if (workDuration.durationNanos <= 0) {
2450                 throw new IllegalArgumentException(
2451                     TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
2452                             workDuration.durationNanos));
2453             }
2454             if (workDuration.workPeriodStartTimestampNanos < 0) {
2455                 throw new IllegalArgumentException(
2456                     TextUtils.formatSimple(
2457                             "Work period start timestamp (%d) should be greater than 0",
2458                             workDuration.workPeriodStartTimestampNanos));
2459             }
2460             if (workDuration.cpuDurationNanos < 0) {
2461                 throw new IllegalArgumentException(
2462                     TextUtils.formatSimple(
2463                         "Actual CPU duration (%d) should be greater than or equal to 0",
2464                             workDuration.cpuDurationNanos));
2465             }
2466             if (workDuration.gpuDurationNanos < 0) {
2467                 throw new IllegalArgumentException(
2468                     TextUtils.formatSimple(
2469                         "Actual GPU duration (%d) should greater than or equal to 0",
2470                             workDuration.gpuDurationNanos));
2471             }
2472             if (workDuration.cpuDurationNanos
2473                     + workDuration.gpuDurationNanos <= 0) {
2474                 throw new IllegalArgumentException(
2475                     TextUtils.formatSimple(
2476                         "The actual CPU duration (%d) and the actual GPU duration (%d)"
2477                         + " should not both be 0", workDuration.cpuDurationNanos,
2478                         workDuration.gpuDurationNanos));
2479             }
2480         }
2481 
pause()2482         private void pause() {
2483             synchronized (this) {
2484                 if (mHalSessionPtr == 0) return;
2485                 mNativeWrapper.halPauseHintSession(mHalSessionPtr);
2486             }
2487         }
2488 
resume()2489         private void resume() {
2490             synchronized (this) {
2491                 if (mHalSessionPtr == 0) return;
2492                 mNativeWrapper.halResumeHintSession(mHalSessionPtr);
2493                 if (mNewThreadIds != null) {
2494                     mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds);
2495                     mThreadIds = mNewThreadIds;
2496                     mNewThreadIds = null;
2497                 }
2498             }
2499         }
2500 
dump(PrintWriter pw, String prefix)2501         private void dump(PrintWriter pw, String prefix) {
2502             synchronized (this) {
2503                 pw.println(prefix + "SessionPID: " + mPid);
2504                 pw.println(prefix + "SessionUID: " + mUid);
2505                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
2506                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
2507                 pw.println(prefix + "SessionAllowedByProcState: " + mUpdateAllowedByProcState);
2508                 pw.println(prefix + "SessionForcePaused: " + mShouldForcePause);
2509                 pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
2510                 pw.println(prefix + "GraphicsPipeline: " + (mGraphicsPipeline ? "true" : "false"));
2511             }
2512         }
2513 
2514         @Override
binderDied()2515         public void binderDied() {
2516             close();
2517         }
2518 
2519     }
2520 }
2521