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 android.app.ActivityManager; 20 import android.app.ActivityManagerInternal; 21 import android.app.IUidObserver; 22 import android.content.Context; 23 import android.os.Binder; 24 import android.os.IBinder; 25 import android.os.IHintManager; 26 import android.os.IHintSession; 27 import android.os.Process; 28 import android.os.RemoteException; 29 import android.util.ArrayMap; 30 import android.util.SparseArray; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.util.DumpUtils; 35 import com.android.internal.util.Preconditions; 36 import com.android.server.FgThread; 37 import com.android.server.LocalServices; 38 import com.android.server.SystemService; 39 import com.android.server.utils.Slogf; 40 41 import java.io.FileDescriptor; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Objects; 47 48 /** An hint service implementation that runs in System Server process. */ 49 public final class HintManagerService extends SystemService { 50 private static final String TAG = "HintManagerService"; 51 private static final boolean DEBUG = false; 52 @VisibleForTesting final long mHintSessionPreferredRate; 53 54 @GuardedBy("mLock") 55 private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions; 56 57 /** Lock to protect HAL handles and listen list. */ 58 private final Object mLock = new Object(); 59 60 @VisibleForTesting final UidObserver mUidObserver; 61 62 private final NativeWrapper mNativeWrapper; 63 64 private final ActivityManagerInternal mAmInternal; 65 66 @VisibleForTesting final IHintManager.Stub mService = new BinderService(); 67 HintManagerService(Context context)68 public HintManagerService(Context context) { 69 this(context, new Injector()); 70 } 71 72 @VisibleForTesting HintManagerService(Context context, Injector injector)73 HintManagerService(Context context, Injector injector) { 74 super(context); 75 mActiveSessions = new ArrayMap<>(); 76 mNativeWrapper = injector.createNativeWrapper(); 77 mNativeWrapper.halInit(); 78 mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); 79 mUidObserver = new UidObserver(); 80 mAmInternal = Objects.requireNonNull( 81 LocalServices.getService(ActivityManagerInternal.class)); 82 } 83 84 @VisibleForTesting 85 static class Injector { createNativeWrapper()86 NativeWrapper createNativeWrapper() { 87 return new NativeWrapper(); 88 } 89 } 90 isHalSupported()91 private boolean isHalSupported() { 92 return mHintSessionPreferredRate != -1; 93 } 94 95 @Override onStart()96 public void onStart() { 97 publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService); 98 } 99 100 @Override onBootPhase(int phase)101 public void onBootPhase(int phase) { 102 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 103 systemReady(); 104 } 105 } 106 systemReady()107 private void systemReady() { 108 Slogf.v(TAG, "Initializing HintManager service..."); 109 try { 110 ActivityManager.getService().registerUidObserver(mUidObserver, 111 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, 112 ActivityManager.PROCESS_STATE_UNKNOWN, null); 113 } catch (RemoteException e) { 114 // ignored; both services live in system_server 115 } 116 117 } 118 119 /** 120 * Wrapper around the static-native methods from native. 121 * 122 * This class exists to allow us to mock static native methods in our tests. If mocking static 123 * methods becomes easier than this in the future, we can delete this class. 124 */ 125 @VisibleForTesting 126 public static class NativeWrapper { nativeInit()127 private native void nativeInit(); 128 nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)129 private static native long nativeCreateHintSession(int tgid, int uid, int[] tids, 130 long durationNanos); 131 nativePauseHintSession(long halPtr)132 private static native void nativePauseHintSession(long halPtr); 133 nativeResumeHintSession(long halPtr)134 private static native void nativeResumeHintSession(long halPtr); 135 nativeCloseHintSession(long halPtr)136 private static native void nativeCloseHintSession(long halPtr); 137 nativeUpdateTargetWorkDuration( long halPtr, long targetDurationNanos)138 private static native void nativeUpdateTargetWorkDuration( 139 long halPtr, long targetDurationNanos); 140 nativeReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)141 private static native void nativeReportActualWorkDuration( 142 long halPtr, long[] actualDurationNanos, long[] timeStampNanos); 143 nativeGetHintSessionPreferredRate()144 private static native long nativeGetHintSessionPreferredRate(); 145 146 /** Wrapper for HintManager.nativeInit */ halInit()147 public void halInit() { 148 nativeInit(); 149 } 150 151 /** Wrapper for HintManager.nativeCreateHintSession */ halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)152 public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) { 153 return nativeCreateHintSession(tgid, uid, tids, durationNanos); 154 } 155 156 /** Wrapper for HintManager.nativePauseHintSession */ halPauseHintSession(long halPtr)157 public void halPauseHintSession(long halPtr) { 158 nativePauseHintSession(halPtr); 159 } 160 161 /** Wrapper for HintManager.nativeResumeHintSession */ halResumeHintSession(long halPtr)162 public void halResumeHintSession(long halPtr) { 163 nativeResumeHintSession(halPtr); 164 } 165 166 /** Wrapper for HintManager.nativeCloseHintSession */ halCloseHintSession(long halPtr)167 public void halCloseHintSession(long halPtr) { 168 nativeCloseHintSession(halPtr); 169 } 170 171 /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */ halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos)172 public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) { 173 nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos); 174 } 175 176 /** Wrapper for HintManager.nativeReportActualWorkDuration */ halReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)177 public void halReportActualWorkDuration( 178 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) { 179 nativeReportActualWorkDuration(halPtr, actualDurationNanos, 180 timeStampNanos); 181 } 182 183 /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */ halGetHintSessionPreferredRate()184 public long halGetHintSessionPreferredRate() { 185 return nativeGetHintSessionPreferredRate(); 186 } 187 } 188 189 @VisibleForTesting 190 final class UidObserver extends IUidObserver.Stub { 191 private final SparseArray<Integer> mProcStatesCache = new SparseArray<>(); 192 isUidForeground(int uid)193 public boolean isUidForeground(int uid) { 194 synchronized (mLock) { 195 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) 196 <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; 197 } 198 } 199 200 @Override onUidGone(int uid, boolean disabled)201 public void onUidGone(int uid, boolean disabled) { 202 FgThread.getHandler().post(() -> { 203 synchronized (mLock) { 204 ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid); 205 if (tokenMap == null) { 206 return; 207 } 208 for (int i = tokenMap.size() - 1; i >= 0; i--) { 209 // Will remove the session from tokenMap 210 tokenMap.valueAt(i).close(); 211 } 212 mProcStatesCache.delete(uid); 213 } 214 }); 215 } 216 217 @Override onUidActive(int uid)218 public void onUidActive(int uid) { 219 } 220 221 @Override onUidIdle(int uid, boolean disabled)222 public void onUidIdle(int uid, boolean disabled) { 223 } 224 225 /** 226 * The IUidObserver callback is called from the system_server, so it'll be a direct function 227 * call from ActivityManagerService. Do not do heavy logic here. 228 */ 229 @Override onUidStateChanged(int uid, int procState, long procStateSeq, int capability)230 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 231 FgThread.getHandler().post(() -> { 232 synchronized (mLock) { 233 mProcStatesCache.put(uid, procState); 234 ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid); 235 if (tokenMap == null) { 236 return; 237 } 238 for (AppHintSession s : tokenMap.values()) { 239 s.onProcStateChanged(); 240 } 241 } 242 }); 243 } 244 245 @Override onUidCachedChanged(int uid, boolean cached)246 public void onUidCachedChanged(int uid, boolean cached) { 247 } 248 } 249 250 @VisibleForTesting getBinderServiceInstance()251 IHintManager.Stub getBinderServiceInstance() { 252 return mService; 253 } 254 checkTidValid(int uid, int tgid, int [] tids)255 private boolean checkTidValid(int uid, int tgid, int [] tids) { 256 // Make sure all tids belongs to the same UID (including isolated UID), 257 // tids can belong to different application processes. 258 List<Integer> eligiblePids = null; 259 // To avoid deadlock, do not call into AMS if the call is from system. 260 if (uid != Process.SYSTEM_UID) { 261 eligiblePids = mAmInternal.getIsolatedProcesses(uid); 262 } 263 if (eligiblePids == null) { 264 eligiblePids = new ArrayList<>(); 265 } 266 eligiblePids.add(tgid); 267 268 for (int threadId : tids) { 269 final String[] procStatusKeys = new String[] { 270 "Uid:", 271 "Tgid:" 272 }; 273 long[] output = new long[procStatusKeys.length]; 274 Process.readProcLines("/proc/" + threadId + "/status", procStatusKeys, output); 275 int uidOfThreadId = (int) output[0]; 276 int pidOfThreadId = (int) output[1]; 277 278 // use PID check for isolated processes, use UID check for non-isolated processes. 279 if (eligiblePids.contains(pidOfThreadId) || uidOfThreadId == uid) { 280 continue; 281 } 282 return false; 283 } 284 return true; 285 } 286 287 @VisibleForTesting 288 final class BinderService extends IHintManager.Stub { 289 @Override createHintSession(IBinder token, int[] tids, long durationNanos)290 public IHintSession createHintSession(IBinder token, int[] tids, long durationNanos) { 291 if (!isHalSupported()) return null; 292 293 java.util.Objects.requireNonNull(token); 294 java.util.Objects.requireNonNull(tids); 295 Preconditions.checkArgument(tids.length != 0, "tids should" 296 + " not be empty."); 297 298 final int callingUid = Binder.getCallingUid(); 299 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 300 final long identity = Binder.clearCallingIdentity(); 301 try { 302 if (!checkTidValid(callingUid, callingTgid, tids)) { 303 throw new SecurityException("Some tid doesn't belong to the application"); 304 } 305 306 long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid, 307 tids, durationNanos); 308 if (halSessionPtr == 0) return null; 309 310 AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token, 311 halSessionPtr, durationNanos); 312 synchronized (mLock) { 313 ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(callingUid); 314 if (tokenMap == null) { 315 tokenMap = new ArrayMap<>(1); 316 mActiveSessions.put(callingUid, tokenMap); 317 } 318 tokenMap.put(token, hs); 319 return hs; 320 } 321 } finally { 322 Binder.restoreCallingIdentity(identity); 323 } 324 } 325 326 @Override getHintSessionPreferredRate()327 public long getHintSessionPreferredRate() { 328 return mHintSessionPreferredRate; 329 } 330 331 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)332 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 333 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { 334 return; 335 } 336 synchronized (mLock) { 337 pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); 338 pw.println("HAL Support: " + isHalSupported()); 339 pw.println("Active Sessions:"); 340 for (int i = 0; i < mActiveSessions.size(); i++) { 341 pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":"); 342 ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i); 343 for (int j = 0; j < tokenMap.size(); j++) { 344 pw.println(" Session " + j + ":"); 345 tokenMap.valueAt(j).dump(pw, " "); 346 } 347 } 348 } 349 } 350 } 351 352 @VisibleForTesting 353 final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { 354 protected final int mUid; 355 protected final int mPid; 356 protected final int[] mThreadIds; 357 protected final IBinder mToken; 358 protected long mHalSessionPtr; 359 protected long mTargetDurationNanos; 360 protected boolean mUpdateAllowed; 361 AppHintSession( int uid, int pid, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos)362 protected AppHintSession( 363 int uid, int pid, int[] threadIds, IBinder token, 364 long halSessionPtr, long durationNanos) { 365 mUid = uid; 366 mPid = pid; 367 mToken = token; 368 mThreadIds = threadIds; 369 mHalSessionPtr = halSessionPtr; 370 mTargetDurationNanos = durationNanos; 371 mUpdateAllowed = true; 372 updateHintAllowed(); 373 try { 374 token.linkToDeath(this, 0); 375 } catch (RemoteException e) { 376 mNativeWrapper.halCloseHintSession(mHalSessionPtr); 377 throw new IllegalStateException("Client already dead", e); 378 } 379 } 380 381 @VisibleForTesting updateHintAllowed()382 boolean updateHintAllowed() { 383 synchronized (mLock) { 384 final boolean allowed = mUidObserver.isUidForeground(mUid); 385 if (allowed && !mUpdateAllowed) resume(); 386 if (!allowed && mUpdateAllowed) pause(); 387 mUpdateAllowed = allowed; 388 return mUpdateAllowed; 389 } 390 } 391 392 @Override updateTargetWorkDuration(long targetDurationNanos)393 public void updateTargetWorkDuration(long targetDurationNanos) { 394 synchronized (mLock) { 395 if (mHalSessionPtr == 0 || !updateHintAllowed()) { 396 return; 397 } 398 Preconditions.checkArgument(targetDurationNanos > 0, "Expected" 399 + " the target duration to be greater than 0."); 400 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); 401 mTargetDurationNanos = targetDurationNanos; 402 } 403 } 404 405 @Override reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos)406 public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) { 407 synchronized (mLock) { 408 if (mHalSessionPtr == 0 || !updateHintAllowed()) { 409 return; 410 } 411 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count" 412 + " of hint durations shouldn't be 0."); 413 Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length, 414 "The length of durations and timestamps should be the same."); 415 for (int i = 0; i < actualDurationNanos.length; i++) { 416 if (actualDurationNanos[i] <= 0) { 417 throw new IllegalArgumentException( 418 String.format("durations[%d]=%d should be greater than 0", 419 i, actualDurationNanos[i])); 420 } 421 } 422 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos, 423 timeStampNanos); 424 } 425 } 426 427 /** TODO: consider monitor session threads and close session if any thread is dead. */ 428 @Override close()429 public void close() { 430 synchronized (mLock) { 431 if (mHalSessionPtr == 0) return; 432 mNativeWrapper.halCloseHintSession(mHalSessionPtr); 433 mHalSessionPtr = 0; 434 mToken.unlinkToDeath(this, 0); 435 ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid); 436 if (tokenMap == null) { 437 Slogf.w(TAG, "UID %d is note present in active session map", mUid); 438 } 439 tokenMap.remove(mToken); 440 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); 441 } 442 } 443 onProcStateChanged()444 private void onProcStateChanged() { 445 updateHintAllowed(); 446 } 447 pause()448 private void pause() { 449 synchronized (mLock) { 450 if (mHalSessionPtr == 0) return; 451 mNativeWrapper.halPauseHintSession(mHalSessionPtr); 452 } 453 } 454 resume()455 private void resume() { 456 synchronized (mLock) { 457 if (mHalSessionPtr == 0) return; 458 mNativeWrapper.halResumeHintSession(mHalSessionPtr); 459 } 460 } 461 dump(PrintWriter pw, String prefix)462 private void dump(PrintWriter pw, String prefix) { 463 synchronized (mLock) { 464 pw.println(prefix + "SessionPID: " + mPid); 465 pw.println(prefix + "SessionUID: " + mUid); 466 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds)); 467 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos); 468 pw.println(prefix + "SessionAllowed: " + updateHintAllowed()); 469 } 470 } 471 472 @Override binderDied()473 public void binderDied() { 474 close(); 475 } 476 477 } 478 } 479