• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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