blob: aa7e6c9c80fc56f85d57c3059f24dac641e091c6 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
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.annotation.NonNull;
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.input.InputManagerService;
import java.io.PrintWriter;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
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;
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();
}
}
}
/**
* Notifies the window manager about an application that is not responding because it has
* no focused window.
*
* Called by the InputManager.
*/
@Override
public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
mService.mAnrController.notifyAppUnresponsive(
applicationHandle, "Application does not have a focused window");
}
@Override
public void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason) {
mService.mAnrController.notifyGestureMonitorUnresponsive(pid, reason);
}
@Override
public void notifyWindowUnresponsive(@NonNull IBinder token, String reason) {
mService.mAnrController.notifyWindowUnresponsive(token, reason);
}
@Override
public void notifyGestureMonitorResponsive(int pid) {
mService.mAnrController.notifyGestureMonitorResponsive(pid);
}
@Override
public void notifyWindowResponsive(@NonNull IBinder token) {
mService.mAnrController.notifyWindowResponsive(token);
}
/** Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
synchronized (mService.mGlobalLock) {
mService.mRoot.forAllDisplays(DisplayContent::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 void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
mService.mH.sendMessage(PooledLambda.obtainMessage(
mService::reportFocusChanged, oldToken, newToken));
}
@Override
public void notifyDropWindow(IBinder token, float x, float y) {
mService.mH.sendMessage(PooledLambda.obtainMessage(
mService.mDragDropController::reportDropWindow, token, x, y));
}
/** 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);
}
}
}