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.WindowManagerDebugConfig.TAG_WM; 23 24 import android.annotation.NonNull; 25 import android.os.Build; 26 import android.os.IBinder; 27 import android.os.Process; 28 import android.os.SystemClock; 29 import android.util.ArrayMap; 30 import android.util.Slog; 31 import android.util.SparseArray; 32 import android.view.InputApplicationHandle; 33 34 import com.android.server.am.ActivityManagerService; 35 import com.android.server.criticalevents.CriticalEventLog; 36 37 import java.io.File; 38 import java.util.ArrayList; 39 import java.util.OptionalInt; 40 import java.util.concurrent.CountDownLatch; 41 import java.util.concurrent.TimeUnit; 42 43 /** 44 * Translates input channel tokens and app tokens to ProcessRecords and PIDs that AMS can use to 45 * blame unresponsive apps. This class also handles dumping WMS state when an app becomes 46 * unresponsive. 47 */ 48 class AnrController { 49 /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */ 50 private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20); 51 /** The timeout to detect if a monitor is held for a while. */ 52 private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); 53 /** The last time pre-dump was executed. */ 54 private volatile long mLastPreDumpTimeMs; 55 56 private final SparseArray<ActivityRecord> mUnresponsiveAppByDisplay = new SparseArray<>(); 57 58 private final WindowManagerService mService; AnrController(WindowManagerService service)59 AnrController(WindowManagerService service) { 60 mService = service; 61 } 62 notifyAppUnresponsive(InputApplicationHandle applicationHandle, String reason)63 void notifyAppUnresponsive(InputApplicationHandle applicationHandle, String reason) { 64 preDumpIfLockTooSlow(); 65 final ActivityRecord activity; 66 synchronized (mService.mGlobalLock) { 67 activity = ActivityRecord.forTokenLocked(applicationHandle.token); 68 if (activity == null) { 69 Slog.e(TAG_WM, "Unknown app appToken:" + applicationHandle.name 70 + ". Dropping notifyNoFocusedWindowAnr request"); 71 return; 72 } 73 Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: " + reason); 74 dumpAnrStateLocked(activity, null /* windowState */, reason); 75 mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity); 76 } 77 activity.inputDispatchingTimedOut(reason, INVALID_PID); 78 } 79 80 81 /** 82 * Notify a window was unresponsive. 83 * 84 * @param token - the input token of the window 85 * @param pid - the pid of the window, if known 86 * @param reason - the reason for the window being unresponsive 87 */ notifyWindowUnresponsive(@onNull IBinder token, @NonNull OptionalInt pid, @NonNull String reason)88 void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid, 89 @NonNull String reason) { 90 if (notifyWindowUnresponsive(token, reason)) { 91 return; 92 } 93 if (!pid.isPresent()) { 94 Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive."); 95 return; 96 } 97 notifyWindowUnresponsive(pid.getAsInt(), reason); 98 } 99 100 /** 101 * Notify a window identified by its input token was unresponsive. 102 * 103 * @return true if the window was identified by the given input token and the request was 104 * handled, false otherwise. 105 */ notifyWindowUnresponsive(@onNull IBinder inputToken, String reason)106 private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) { 107 preDumpIfLockTooSlow(); 108 final int pid; 109 final boolean aboveSystem; 110 final ActivityRecord activity; 111 synchronized (mService.mGlobalLock) { 112 InputTarget target = mService.getInputTargetFromToken(inputToken); 113 if (target == null) { 114 return false; 115 } 116 WindowState windowState = target.getWindowState(); 117 pid = target.getPid(); 118 // Blame the activity if the input token belongs to the window. If the target is 119 // embedded, then we will blame the pid instead. 120 activity = (windowState.mInputChannelToken == inputToken) 121 ? windowState.mActivityRecord : null; 122 Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason); 123 aboveSystem = isWindowAboveSystem(windowState); 124 dumpAnrStateLocked(activity, windowState, reason); 125 } 126 if (activity != null) { 127 activity.inputDispatchingTimedOut(reason, pid); 128 } else { 129 mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason); 130 } 131 return true; 132 } 133 134 /** 135 * Notify a window owned by the provided pid was unresponsive. 136 */ notifyWindowUnresponsive(int pid, String reason)137 private void notifyWindowUnresponsive(int pid, String reason) { 138 Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason); 139 dumpAnrStateLocked(null /* activity */, null /* windowState */, reason); 140 141 // We cannot determine the z-order of the window, so place the anr dialog as high 142 // as possible. 143 mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason); 144 } 145 146 /** 147 * Notify a window was responsive after previously being unresponsive. 148 * 149 * @param token - the input token of the window 150 * @param pid - the pid of the window, if known 151 */ notifyWindowResponsive(@onNull IBinder token, @NonNull OptionalInt pid)152 void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) { 153 if (notifyWindowResponsive(token)) { 154 return; 155 } 156 if (!pid.isPresent()) { 157 Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive."); 158 return; 159 } 160 notifyWindowResponsive(pid.getAsInt()); 161 } 162 163 /** 164 * Notify a window identified by its input token was responsive after previously being 165 * unresponsive. 166 * 167 * @return true if the window was identified by the given input token and the request was 168 * handled, false otherwise. 169 */ notifyWindowResponsive(@onNull IBinder inputToken)170 private boolean notifyWindowResponsive(@NonNull IBinder inputToken) { 171 final int pid; 172 synchronized (mService.mGlobalLock) { 173 InputTarget target = mService.getInputTargetFromToken(inputToken); 174 if (target == null) { 175 return false; 176 } 177 pid = target.getPid(); 178 } 179 mService.mAmInternal.inputDispatchingResumed(pid); 180 return true; 181 } 182 183 /** 184 * Notify a window owned by the provided pid was responsive after previously being unresponsive. 185 */ notifyWindowResponsive(int pid)186 private void notifyWindowResponsive(int pid) { 187 mService.mAmInternal.inputDispatchingResumed(pid); 188 } 189 190 /** 191 * If we reported an unresponsive apps to AMS, notify AMS that the app is now responsive if a 192 * window belonging to the app gets focused. 193 * <p> 194 * @param newFocus new focused window 195 */ onFocusChanged(WindowState newFocus)196 void onFocusChanged(WindowState newFocus) { 197 ActivityRecord unresponsiveApp; 198 synchronized (mService.mGlobalLock) { 199 unresponsiveApp = mUnresponsiveAppByDisplay.get(newFocus.getDisplayId()); 200 if (unresponsiveApp == null || unresponsiveApp != newFocus.mActivityRecord) { 201 return; 202 } 203 } 204 mService.mAmInternal.inputDispatchingResumed(unresponsiveApp.getPid()); 205 } 206 207 /** 208 * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked 209 * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces 210 * before the real blocking symptom has gone. 211 * <p> 212 * Do not hold the {@link WindowManagerGlobalLock} while calling this method. 213 */ preDumpIfLockTooSlow()214 private void preDumpIfLockTooSlow() { 215 if (!Build.IS_DEBUGGABLE) { 216 return; 217 } 218 final long now = SystemClock.uptimeMillis(); 219 if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) { 220 return; 221 } 222 223 final boolean[] shouldDumpSf = { true }; 224 final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2); 225 monitors.put(TAG_WM, mService::monitor); 226 monitors.put("ActivityManager", mService.mAmInternal::monitor); 227 final CountDownLatch latch = new CountDownLatch(monitors.size()); 228 // The pre-dump will execute if one of the monitors doesn't complete within the timeout. 229 for (int i = 0; i < monitors.size(); i++) { 230 final String name = monitors.keyAt(i); 231 final Runnable monitor = monitors.valueAt(i); 232 // Always create new thread to avoid noise of existing threads. Suppose here won't 233 // create too many threads because it means that watchdog will be triggered first. 234 new Thread() { 235 @Override 236 public void run() { 237 monitor.run(); 238 latch.countDown(); 239 final long elapsed = SystemClock.uptimeMillis() - now; 240 if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) { 241 Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms"); 242 } else if (TAG_WM.equals(name)) { 243 // Window manager is the main client of SurfaceFlinger. If window manager 244 // is responsive, the stack traces of SurfaceFlinger may not be important. 245 shouldDumpSf[0] = false; 246 } 247 }; 248 }.start(); 249 } 250 try { 251 if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 252 return; 253 } 254 } catch (InterruptedException ignored) { } 255 mLastPreDumpTimeMs = now; 256 Slog.i(TAG_WM, "Pre-dump for unresponsive"); 257 258 final ArrayList<Integer> firstPids = new ArrayList<>(1); 259 firstPids.add(WindowManagerService.MY_PID); 260 ArrayList<Integer> nativePids = null; 261 final int[] pids = shouldDumpSf[0] 262 ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" }) 263 : null; 264 if (pids != null) { 265 nativePids = new ArrayList<>(1); 266 for (int pid : pids) { 267 nativePids.add(pid); 268 } 269 } 270 271 String criticalEvents = CriticalEventLog.getInstance().logLinesForSystemServerTraceFile(); 272 final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, 273 null /* processCpuTracker */, null /* lastPids */, nativePids, 274 null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents); 275 if (tracesFile != null) { 276 tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre")); 277 } 278 } 279 dumpAnrStateLocked(ActivityRecord activity, WindowState windowState, String reason)280 private void dumpAnrStateLocked(ActivityRecord activity, WindowState windowState, 281 String reason) { 282 mService.saveANRStateLocked(activity, windowState, reason); 283 mService.mAtmService.saveANRState(reason); 284 } 285 isWindowAboveSystem(@onNull WindowState windowState)286 private boolean isWindowAboveSystem(@NonNull WindowState windowState) { 287 int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw( 288 TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow); 289 return windowState.mBaseLayer > systemAlertLayer; 290 } 291 } 292