1 /* 2 * Copyright (C) 2020 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 package com.android.server.wm; 17 18 19 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 20 21 import static com.android.server.wm.ActivityRecord.INVALID_PID; 22 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked; 23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 24 25 import android.annotation.NonNull; 26 import android.os.Build; 27 import android.os.IBinder; 28 import android.os.Process; 29 import android.os.SystemClock; 30 import android.os.Trace; 31 import android.util.ArrayMap; 32 import android.util.Slog; 33 import android.util.SparseArray; 34 import android.view.InputApplicationHandle; 35 36 import com.android.internal.os.TimeoutRecord; 37 import com.android.server.FgThread; 38 import com.android.server.am.StackTracesDumpHelper; 39 import com.android.server.criticalevents.CriticalEventLog; 40 41 import java.io.File; 42 import java.util.ArrayList; 43 import java.util.OptionalInt; 44 import java.util.concurrent.CompletableFuture; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.TimeUnit; 47 48 /** 49 * Translates input channel tokens and app tokens to ProcessRecords and PIDs that AMS can use to 50 * blame unresponsive apps. This class also handles dumping WMS state when an app becomes 51 * unresponsive. 52 */ 53 class AnrController { 54 /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */ 55 private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20); 56 /** The timeout to detect if a monitor is held for a while. */ 57 private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); 58 /** The last time pre-dump was executed. */ 59 private volatile long mLastPreDumpTimeMs; 60 61 private final SparseArray<ActivityRecord> mUnresponsiveAppByDisplay = new SparseArray<>(); 62 63 private final WindowManagerService mService; AnrController(WindowManagerService service)64 AnrController(WindowManagerService service) { 65 mService = service; 66 } 67 notifyAppUnresponsive(InputApplicationHandle applicationHandle, TimeoutRecord timeoutRecord)68 void notifyAppUnresponsive(InputApplicationHandle applicationHandle, 69 TimeoutRecord timeoutRecord) { 70 try { 71 timeoutRecord.mLatencyTracker.notifyAppUnresponsiveStarted(); 72 timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted(); 73 preDumpIfLockTooSlow(); 74 timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded(); 75 final ActivityRecord activity; 76 timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted(); 77 boolean blamePendingFocusRequest = false; 78 IBinder focusToken = null; 79 WindowState targetWindowState = null; 80 synchronized (mService.mGlobalLock) { 81 timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded(); 82 activity = ActivityRecord.forTokenLocked(applicationHandle.token); 83 if (activity == null) { 84 Slog.e(TAG_WM, "Unknown app appToken:" + applicationHandle.name 85 + ". Dropping notifyNoFocusedWindowAnr request"); 86 return; 87 } else if (activity.mAppStopped) { 88 Slog.d(TAG_WM, "App is in stopped state:" + applicationHandle.name 89 + ". Dropping notifyNoFocusedWindowAnr request"); 90 return; 91 } 92 93 // App is unresponsive, but we are actively trying to give focus to a window. 94 // Blame the window if possible since the window may not belong to the app. 95 DisplayContent display = mService.mRoot.getDisplayContent(activity.getDisplayId()); 96 if (display != null) { 97 focusToken = display.getInputMonitor().mInputFocus; 98 } 99 InputTarget focusTarget = mService.getInputTargetFromToken(focusToken); 100 101 if (focusTarget != null) { 102 // Check if we have a recent focus request, newer than the dispatch timeout, 103 // then ignore the focus request. 104 targetWindowState = focusTarget.getWindowState(); 105 blamePendingFocusRequest = SystemClock.uptimeMillis() 106 - display.getInputMonitor().mInputFocusRequestTimeMillis 107 >= getInputDispatchingTimeoutMillisLocked( 108 targetWindowState.getActivityRecord()); 109 } 110 111 if (!blamePendingFocusRequest) { 112 Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: " 113 + timeoutRecord.mReason); 114 mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity); 115 } 116 } 117 118 if (blamePendingFocusRequest && notifyWindowUnresponsive(focusToken, timeoutRecord)) { 119 Slog.i(TAG_WM, "Blamed " + targetWindowState.getName() 120 + " using pending focus request. Focused activity: " 121 + activity.getName()); 122 } else { 123 activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID); 124 } 125 126 if (!blamePendingFocusRequest) { 127 dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason); 128 } 129 130 } finally { 131 timeoutRecord.mLatencyTracker.notifyAppUnresponsiveEnded(); 132 } 133 } 134 135 136 /** 137 * Notify a window was unresponsive. 138 * 139 * @param token - the input token of the window 140 * @param pid - the pid of the window, if known 141 * @param timeoutRecord - details for the timeout 142 */ notifyWindowUnresponsive(@onNull IBinder token, @NonNull OptionalInt pid, @NonNull TimeoutRecord timeoutRecord)143 void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid, 144 @NonNull TimeoutRecord timeoutRecord) { 145 try { 146 timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveStarted(); 147 if (notifyWindowUnresponsive(token, timeoutRecord)) { 148 return; 149 } 150 if (!pid.isPresent()) { 151 Slog.w(TAG_WM, "Failed to notify that window token=" + token 152 + " was unresponsive."); 153 return; 154 } 155 notifyWindowUnresponsive(pid.getAsInt(), timeoutRecord); 156 } finally { 157 timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveEnded(); 158 } 159 } 160 161 /** 162 * Notify a window identified by its input token was unresponsive. 163 * 164 * @return true if the window was identified by the given input token and the request was 165 * handled, false otherwise. 166 */ notifyWindowUnresponsive(@onNull IBinder inputToken, TimeoutRecord timeoutRecord)167 private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, 168 TimeoutRecord timeoutRecord) { 169 timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted(); 170 preDumpIfLockTooSlow(); 171 timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded(); 172 final int pid; 173 final boolean aboveSystem; 174 final ActivityRecord activity; 175 final WindowState windowState; 176 timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted(); 177 synchronized (mService.mGlobalLock) { 178 timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded(); 179 InputTarget target = mService.getInputTargetFromToken(inputToken); 180 if (target == null) { 181 return false; 182 } 183 windowState = target.getWindowState(); 184 pid = target.getPid(); 185 // Blame the activity if the input token belongs to the window. If the target is 186 // embedded, then we will blame the pid instead. 187 activity = (windowState.mInputChannelToken == inputToken) 188 ? windowState.mActivityRecord : null; 189 Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason); 190 aboveSystem = isWindowAboveSystem(windowState); 191 } 192 if (activity != null) { 193 activity.inputDispatchingTimedOut(timeoutRecord, pid); 194 } else { 195 mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, timeoutRecord); 196 } 197 dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason); 198 return true; 199 } 200 201 /** 202 * Notify a window owned by the provided pid was unresponsive. 203 */ notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord)204 private void notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord) { 205 Slog.i(TAG_WM, 206 "ANR in input window owned by pid=" + pid + ". Reason: " + timeoutRecord.mReason); 207 // We cannot determine the z-order of the window, so place the anr dialog as high 208 // as possible. 209 mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, timeoutRecord); 210 dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason); 211 } 212 213 /** 214 * Notify a window was responsive after previously being unresponsive. 215 * 216 * @param token - the input token of the window 217 * @param pid - the pid of the window, if known 218 */ notifyWindowResponsive(@onNull IBinder token, @NonNull OptionalInt pid)219 void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) { 220 if (notifyWindowResponsive(token)) { 221 return; 222 } 223 if (!pid.isPresent()) { 224 Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive."); 225 return; 226 } 227 notifyWindowResponsive(pid.getAsInt()); 228 } 229 230 /** 231 * Notify a window identified by its input token was responsive after previously being 232 * unresponsive. 233 * 234 * @return true if the window was identified by the given input token and the request was 235 * handled, false otherwise. 236 */ notifyWindowResponsive(@onNull IBinder inputToken)237 private boolean notifyWindowResponsive(@NonNull IBinder inputToken) { 238 final int pid; 239 synchronized (mService.mGlobalLock) { 240 InputTarget target = mService.getInputTargetFromToken(inputToken); 241 if (target == null) { 242 return false; 243 } 244 pid = target.getPid(); 245 } 246 mService.mAmInternal.inputDispatchingResumed(pid); 247 return true; 248 } 249 250 /** 251 * Notify a window owned by the provided pid was responsive after previously being unresponsive. 252 */ notifyWindowResponsive(int pid)253 private void notifyWindowResponsive(int pid) { 254 mService.mAmInternal.inputDispatchingResumed(pid); 255 } 256 257 /** 258 * If we reported an unresponsive apps to AMS, notify AMS that the app is now responsive if a 259 * window belonging to the app gets focused. 260 * <p> 261 * @param newFocus new focused window 262 */ onFocusChanged(WindowState newFocus)263 void onFocusChanged(WindowState newFocus) { 264 ActivityRecord unresponsiveApp; 265 synchronized (mService.mGlobalLock) { 266 unresponsiveApp = mUnresponsiveAppByDisplay.get(newFocus.getDisplayId()); 267 if (unresponsiveApp == null || unresponsiveApp != newFocus.mActivityRecord) { 268 return; 269 } 270 } 271 mService.mAmInternal.inputDispatchingResumed(unresponsiveApp.getPid()); 272 mUnresponsiveAppByDisplay.remove(newFocus.getDisplayId()); 273 } 274 275 /** 276 * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked 277 * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces 278 * before the real blocking symptom has gone. 279 * <p> 280 * Do not hold the {@link WindowManagerGlobalLock} while calling this method. 281 */ preDumpIfLockTooSlow()282 private void preDumpIfLockTooSlow() { 283 if (!Build.IS_DEBUGGABLE) { 284 return; 285 } 286 final long now = SystemClock.uptimeMillis(); 287 if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) { 288 return; 289 } 290 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "preDumpIfLockTooSlow()"); 291 try { 292 final boolean[] shouldDumpSf = { true }; 293 final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2); 294 monitors.put(TAG_WM, mService::monitor); 295 monitors.put("ActivityManager", mService.mAmInternal::monitor); 296 final CountDownLatch latch = new CountDownLatch(monitors.size()); 297 // The pre-dump will execute if one of the monitors doesn't complete within 298 // the timeout. 299 for (int i = 0; i < monitors.size(); i++) { 300 final String name = monitors.keyAt(i); 301 final Runnable monitor = monitors.valueAt(i); 302 // Always create new thread to avoid noise of existing threads. Suppose here won't 303 // create too many threads because it means that watchdog will be triggered first. 304 new Thread() { 305 @Override 306 public void run() { 307 monitor.run(); 308 latch.countDown(); 309 final long elapsed = SystemClock.uptimeMillis() - now; 310 if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) { 311 Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms"); 312 } else if (TAG_WM.equals(name)) { 313 // Window manager is the main client of SurfaceFlinger. 314 // If window manager is responsive, the stack traces 315 // of SurfaceFlinger may not be important. 316 shouldDumpSf[0] = false; 317 } 318 }; 319 }.start(); 320 } 321 try { 322 if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 323 return; 324 } 325 } catch (InterruptedException ignored) { } 326 mLastPreDumpTimeMs = now; 327 Slog.i(TAG_WM, "Pre-dump for unresponsive"); 328 329 final ArrayList<Integer> firstPids = new ArrayList<>(1); 330 firstPids.add(WindowManagerService.MY_PID); 331 ArrayList<Integer> nativePids = null; 332 final int[] pids = shouldDumpSf[0] 333 ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" }) 334 : null; 335 if (pids != null) { 336 nativePids = new ArrayList<>(1); 337 for (int pid : pids) { 338 nativePids.add(pid); 339 } 340 } 341 342 String criticalEvents = 343 CriticalEventLog.getInstance().logLinesForSystemServerTraceFile(); 344 final File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids, 345 null /* processCpuTracker */, null /* lastPids */, 346 CompletableFuture.completedFuture(nativePids), 347 null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents, 348 Runnable::run, null/* AnrLatencyTracker */); 349 if (tracesFile != null) { 350 tracesFile.renameTo( 351 new File(tracesFile.getParent(), tracesFile.getName() + "_pre")); 352 } 353 } finally { 354 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 355 } 356 357 } 358 359 /** 360 * Executes asynchronously on the fg thread not to block the stack dump for 361 * the ANRing processes. 362 */ dumpAnrStateAsync(ActivityRecord activity, WindowState windowState, String reason)363 private void dumpAnrStateAsync(ActivityRecord activity, WindowState windowState, 364 String reason) { 365 FgThread.getExecutor().execute(() -> { 366 try { 367 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "dumpAnrStateLocked()"); 368 synchronized (mService.mGlobalLock) { 369 mService.saveANRStateLocked(activity, windowState, reason); 370 mService.mAtmService.saveANRState(reason); 371 } 372 } finally { 373 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 374 } 375 }); 376 } 377 isWindowAboveSystem(@onNull WindowState windowState)378 private boolean isWindowAboveSystem(@NonNull WindowState windowState) { 379 int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw( 380 TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow); 381 return windowState.mBaseLayer > systemAlertLayer; 382 } 383 } 384