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