blob: 9c4ac890fed879b228f90ea674f9ab9679738678 [file] [log] [blame]
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.server.wm.ActivityRecord.INVALID_PID;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.android.server.am.ActivityManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
/** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */
private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
/** The timeout to detect if a monitor is held for a while. */
private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
/** The last time pre-dump was executed. */
private volatile long mLastPreDumpTimeMs;
private final WindowManagerService mService;
// Set to true when the first input device configuration change notification
// is received to indicate that the input devices are ready.
private final Object mInputDevicesReadyMonitor = new Object();
private boolean mInputDevicesReady;
// When true, prevents input dispatch from proceeding until set to false again.
private boolean mInputDispatchFrozen;
// The reason the input is currently frozen or null if the input isn't frozen.
private String mInputFreezeReason = null;
// When true, input dispatch proceeds normally. Otherwise all events are dropped.
// Initially false, so that input does not get dispatched until boot is finished at
// which point the ActivityManager will enable dispatching.
private boolean mInputDispatchEnabled;
// TODO(b/141749603)) investigate if this can be part of client focus change dispatch
// Tracks the currently focused window used to update pointer capture state in clients
private AtomicReference<IWindow> mFocusedWindow = new AtomicReference<>();
// Tracks focused window pointer capture state
private boolean mFocusedWindowHasCapture;
public InputManagerCallback(WindowManagerService service) {
mService = service;
}
/**
* Notifies the window manager about a broken input channel.
*
* Called by the InputManager.
*/
@Override
public void notifyInputChannelBroken(IBinder token) {
if (token == null) {
return;
}
synchronized (mService.mGlobalLock) {
WindowState windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
Slog.i(TAG_WM, "WINDOW DIED " + windowState);
windowState.removeIfPossible();
}
}
}
/**
* Pre-dump stack trace if the locks of activity manager or window manager (they may be locked
* in the path of reporting ANR) cannot be acquired in time. That provides the stack traces
* before the real blocking symptom has gone.
* <p>
* Do not hold the {@link WindowManagerGlobalLock} while calling this method.
*/
private void preDumpIfLockTooSlow() {
if (!Build.IS_DEBUGGABLE) {
return;
}
final long now = SystemClock.uptimeMillis();
if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) {
return;
}
final boolean[] shouldDumpSf = { true };
final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2);
monitors.put(TAG_WM, mService::monitor);
monitors.put("ActivityManager", mService.mAmInternal::monitor);
final CountDownLatch latch = new CountDownLatch(monitors.size());
// The pre-dump will execute if one of the monitors doesn't complete within the timeout.
for (int i = 0; i < monitors.size(); i++) {
final String name = monitors.keyAt(i);
final Runnable monitor = monitors.valueAt(i);
// Always create new thread to avoid noise of existing threads. Suppose here won't
// create too many threads because it means that watchdog will be triggered first.
new Thread() {
@Override
public void run() {
monitor.run();
latch.countDown();
final long elapsed = SystemClock.uptimeMillis() - now;
if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) {
Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms");
} else if (TAG_WM.equals(name)) {
// Window manager is the main client of SurfaceFlinger. If window manager
// is responsive, the stack traces of SurfaceFlinger may not be important.
shouldDumpSf[0] = false;
}
};
}.start();
}
try {
if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
return;
}
} catch (InterruptedException ignored) { }
mLastPreDumpTimeMs = now;
Slog.i(TAG_WM, "Pre-dump for unresponsive");
final ArrayList<Integer> firstPids = new ArrayList<>(1);
firstPids.add(ActivityManagerService.MY_PID);
ArrayList<Integer> nativePids = null;
final int[] pids = shouldDumpSf[0]
? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" })
: null;
if (pids != null) {
nativePids = new ArrayList<>(1);
for (int pid : pids) {
nativePids.add(pid);
}
}
final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
null /* processCpuTracker */, null /* lastPids */, nativePids,
null /* logExceptionCreatingFile */);
if (tracesFile != null) {
tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
}
}
/**
* Notifies the window manager about an application that is not responding.
* Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
*
* Called by the InputManager.
*/
@Override
public long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
final long startTime = SystemClock.uptimeMillis();
try {
return notifyANRInner(inputApplicationHandle, token, reason);
} finally {
// Log the time because the method is called from InputDispatcher thread. It shouldn't
// take too long that may affect input response time.
Slog.d(TAG_WM, "notifyANR took " + (SystemClock.uptimeMillis() - startTime) + "ms");
}
}
private long notifyANRInner(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
ActivityRecord activity = null;
WindowState windowState = null;
boolean aboveSystem = false;
int windowPid = INVALID_PID;
preDumpIfLockTooSlow();
//TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
// Check if we can blame a window
if (token != null) {
windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
activity = windowState.mActivityRecord;
windowPid = windowState.mSession.mPid;
// Figure out whether this window is layered above system windows.
// We need to do this here to help the activity manager know how to
// layer its ANR dialog.
aboveSystem = isWindowAboveSystem(windowState);
}
}
// Check if we can blame an embedded window
if (token != null && windowState == null) {
EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(token);
if (embeddedWindow != null) {
windowPid = embeddedWindow.mOwnerPid;
WindowState hostWindowState = embeddedWindow.mHostWindowState;
if (hostWindowState == null) {
// The embedded window has no host window and we cannot easily determine
// its z order. Try to place the anr dialog as high as possible.
aboveSystem = true;
} else {
aboveSystem = isWindowAboveSystem(hostWindowState);
}
}
}
// Check if we can blame an activity. If we don't have an activity to blame, pull out
// the token passed in via input application handle. This can happen if there are no
// focused windows but input dispatcher knows the focused app.
if (activity == null && inputApplicationHandle != null) {
activity = ActivityRecord.forTokenLocked(inputApplicationHandle.token);
}
if (windowState != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to " + windowState.mAttrs.getTitle()
+ ". Reason: " + reason);
} else if (activity != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to application " + activity.stringName
+ ". Reason: " + reason);
} else {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ ". Reason: " + reason);
}
mService.saveANRStateLocked(activity, windowState, reason);
}
// All the calls below need to happen without the WM lock held since they call into AM.
mService.mAtmInternal.saveANRState(reason);
if (activity != null && activity.appToken != null) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
final boolean abort = activity.keyDispatchingTimedOut(reason, windowPid);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return activity.mInputDispatchingTimeoutNanos;
}
} else if (windowState != null || windowPid != INVALID_PID) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
long timeout = mService.mAmInternal.inputDispatchingTimedOut(windowPid, aboveSystem,
reason);
if (timeout >= 0) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return timeout * 1000000L; // nanoseconds
}
}
return 0; // abort dispatching
}
private boolean isWindowAboveSystem(WindowState windowState) {
int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
return windowState.mBaseLayer > systemAlertLayer;
}
/** Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
// TODO(multi-display): Notify proper displays that are associated with this input device.
synchronized (mService.mGlobalLock) {
mService.getDefaultDisplayContentLocked().sendNewConfiguration();
}
synchronized (mInputDevicesReadyMonitor) {
if (!mInputDevicesReady) {
mInputDevicesReady = true;
mInputDevicesReadyMonitor.notifyAll();
}
}
}
/** Notifies that the lid switch changed state. */
@Override
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
}
/** Notifies that the camera lens cover state has changed. */
@Override
public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
mService.mPolicy.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
}
/**
* Provides an opportunity for the window manager policy to intercept early key
* processing as soon as the key has been read from the device.
*/
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
}
/** {@inheritDoc} */
@Override
public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
int policyFlags) {
return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
displayId, whenNanos, policyFlags);
}
/**
* Provides an opportunity for the window manager policy to process a key before
* ordinary dispatch.
*/
@Override
public long interceptKeyBeforeDispatching(
IBinder focusedToken, KeyEvent event, int policyFlags) {
return mService.mPolicy.interceptKeyBeforeDispatching(focusedToken, event, policyFlags);
}
/**
* Provides an opportunity for the window manager policy to process a key that
* the application did not handle.
*/
@Override
public KeyEvent dispatchUnhandledKey(
IBinder focusedToken, KeyEvent event, int policyFlags) {
return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags);
}
/** Callback to get pointer layer. */
@Override
public int getPointerLayer() {
return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
}
/** Callback to get pointer display id. */
@Override
public int getPointerDisplayId() {
synchronized (mService.mGlobalLock) {
// If desktop mode is not enabled, show on the default display.
if (!mService.mForceDesktopModeOnExternalDisplays) {
return DEFAULT_DISPLAY;
}
// Look for the topmost freeform display.
int firstExternalDisplayId = DEFAULT_DISPLAY;
for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
// Heuristic solution here. Currently when "Freeform windows" developer option is
// enabled we automatically put secondary displays in freeform mode and emulating
// "desktop mode". It also makes sense to show the pointer on the same display.
if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return displayContent.getDisplayId();
}
if (firstExternalDisplayId == DEFAULT_DISPLAY
&& displayContent.getDisplayId() != DEFAULT_DISPLAY) {
firstExternalDisplayId = displayContent.getDisplayId();
}
}
// Look for the topmost non-default display
return firstExternalDisplayId;
}
}
@Override
public void onPointerDownOutsideFocus(IBinder touchedToken) {
mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
}
@Override
public boolean notifyFocusChanged(IBinder oldToken, IBinder newToken) {
boolean requestRefreshConfiguration = false;
final IWindow newFocusedWindow;
final WindowState win;
// TODO(b/141749603) investigate if this can be part of client focus change dispatch
synchronized (mService.mGlobalLock) {
win = mService.mInputToWindowMap.get(newToken);
}
newFocusedWindow = (win != null) ? win.mClient : null;
final IWindow focusedWindow = mFocusedWindow.get();
if (focusedWindow != null) {
if (newFocusedWindow != null
&& newFocusedWindow.asBinder() == focusedWindow.asBinder()) {
Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+ focusedWindow);
return false;
}
requestRefreshConfiguration = dispatchPointerCaptureChanged(focusedWindow, false);
}
mFocusedWindow.set(newFocusedWindow);
return requestRefreshConfiguration;
}
@Override
public boolean requestPointerCapture(IBinder windowToken, boolean enabled) {
final IWindow focusedWindow = mFocusedWindow.get();
if (focusedWindow == null || focusedWindow.asBinder() != windowToken) {
Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+ windowToken);
return false;
}
if (mFocusedWindowHasCapture == enabled) {
Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
return false;
}
return dispatchPointerCaptureChanged(focusedWindow, enabled);
}
private boolean dispatchPointerCaptureChanged(IWindow focusedWindow, boolean enabled) {
if (mFocusedWindowHasCapture != enabled) {
mFocusedWindowHasCapture = enabled;
try {
focusedWindow.dispatchPointerCaptureChanged(enabled);
} catch (RemoteException ex) {
/* ignore */
}
return true;
}
return false;
}
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
if (!mInputDevicesReady) {
try {
mInputDevicesReadyMonitor.wait(timeoutMillis);
} catch (InterruptedException ex) {
}
}
return mInputDevicesReady;
}
}
public void freezeInputDispatchingLw() {
if (!mInputDispatchFrozen) {
if (DEBUG_INPUT) {
Slog.v(TAG_WM, "Freezing input dispatching");
}
mInputDispatchFrozen = true;
if (DEBUG_INPUT) {
mInputFreezeReason = Debug.getCallers(6);
}
updateInputDispatchModeLw();
}
}
public void thawInputDispatchingLw() {
if (mInputDispatchFrozen) {
if (DEBUG_INPUT) {
Slog.v(TAG_WM, "Thawing input dispatching");
}
mInputDispatchFrozen = false;
mInputFreezeReason = null;
updateInputDispatchModeLw();
}
}
public void setEventDispatchingLw(boolean enabled) {
if (mInputDispatchEnabled != enabled) {
if (DEBUG_INPUT) {
Slog.v(TAG_WM, "Setting event dispatching to " + enabled);
}
mInputDispatchEnabled = enabled;
updateInputDispatchModeLw();
}
}
private void updateInputDispatchModeLw() {
mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
}
void dump(PrintWriter pw, String prefix) {
if (mInputFreezeReason != null) {
pw.println(prefix + "mInputFreezeReason=" + mInputFreezeReason);
}
}
}