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