/*
|
* Copyright (C) 2010 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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
|
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
|
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
|
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
|
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
|
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
|
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
|
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
|
import android.graphics.Rect;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.Looper;
|
import android.os.Process;
|
import android.os.Trace;
|
import android.os.UserHandle;
|
import android.util.ArrayMap;
|
import android.util.Log;
|
import android.util.Slog;
|
import android.view.InputApplicationHandle;
|
import android.view.InputChannel;
|
import android.view.InputEventReceiver;
|
import android.view.InputWindowHandle;
|
import android.view.SurfaceControl;
|
|
import com.android.server.AnimationThread;
|
import com.android.server.policy.WindowManagerPolicy;
|
|
import java.io.PrintWriter;
|
import java.util.Set;
|
import java.util.function.Consumer;
|
|
final class InputMonitor {
|
private final WindowManagerService mService;
|
|
// Current window with input focus for keys and other non-touch events. May be null.
|
private WindowState mInputFocus;
|
|
// When true, need to call updateInputWindowsLw().
|
private boolean mUpdateInputWindowsNeeded = true;
|
private boolean mUpdateInputWindowsPending;
|
private boolean mApplyImmediately;
|
|
// Currently focused input window handle.
|
private InputWindowHandle mFocusedInputWindowHandle;
|
|
private boolean mDisableWallpaperTouchEvents;
|
private final Rect mTmpRect = new Rect();
|
private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
|
|
private final int mDisplayId;
|
private final DisplayContent mDisplayContent;
|
private boolean mDisplayRemoved;
|
|
private final SurfaceControl.Transaction mInputTransaction;
|
private final Handler mHandler;
|
|
/**
|
* The set of input consumer added to the window manager by name, which consumes input events
|
* for the windows below it.
|
*/
|
private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap();
|
|
private static final class EventReceiverInputConsumer extends InputConsumerImpl
|
implements WindowManagerPolicy.InputConsumer {
|
private InputMonitor mInputMonitor;
|
private final InputEventReceiver mInputEventReceiver;
|
|
EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
|
Looper looper, String name,
|
InputEventReceiver.Factory inputEventReceiverFactory,
|
int clientPid, UserHandle clientUser, int displayId) {
|
super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser,
|
displayId);
|
mInputMonitor = monitor;
|
mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
|
mClientChannel, looper);
|
}
|
|
@Override
|
public void dismiss() {
|
synchronized (mService.mGlobalLock) {
|
if (mInputMonitor.destroyInputConsumer(mWindowHandle.name)) {
|
mInputEventReceiver.dispose();
|
}
|
}
|
}
|
}
|
|
private final Runnable mUpdateInputWindows = new Runnable() {
|
@Override
|
public void run() {
|
synchronized (mService.mGlobalLock) {
|
mUpdateInputWindowsPending = false;
|
mUpdateInputWindowsNeeded = false;
|
|
if (mDisplayRemoved) {
|
return;
|
}
|
|
// Populate the input window list with information about all of the windows that
|
// could potentially receive input.
|
// As an optimization, we could try to prune the list of windows but this turns
|
// out to be difficult because only the native code knows for sure which window
|
// currently has touch focus.
|
|
// If there's a drag in flight, provide a pseudo-window to catch drag input
|
final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();
|
final boolean inPositioning =
|
mService.mTaskPositioningController.isPositioningLocked();
|
if (inPositioning) {
|
if (DEBUG_TASK_POSITIONING) {
|
Log.d(TAG_WM, "Inserting window handle for repositioning");
|
}
|
mService.mTaskPositioningController.showInputSurface(mInputTransaction,
|
mDisplayId);
|
} else {
|
mService.mTaskPositioningController.hideInputSurface(mInputTransaction,
|
mDisplayId);
|
}
|
|
// Add all windows on the default display.
|
mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
|
}
|
}
|
};
|
|
public InputMonitor(WindowManagerService service, int displayId) {
|
mService = service;
|
mDisplayContent = mService.mRoot.getDisplayContent(displayId);
|
mDisplayId = displayId;
|
mInputTransaction = mService.mTransactionFactory.make();
|
mHandler = AnimationThread.getHandler();
|
mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
|
}
|
|
void onDisplayRemoved() {
|
mHandler.removeCallbacks(mUpdateInputWindows);
|
mService.mInputManager.onDisplayRemoved(mDisplayId);
|
mDisplayRemoved = true;
|
}
|
|
private void addInputConsumer(String name, InputConsumerImpl consumer) {
|
mInputConsumers.put(name, consumer);
|
consumer.linkToDeathRecipient();
|
updateInputWindowsLw(true /* force */);
|
}
|
|
boolean destroyInputConsumer(String name) {
|
if (disposeInputConsumer(mInputConsumers.remove(name))) {
|
updateInputWindowsLw(true /* force */);
|
return true;
|
}
|
return false;
|
}
|
|
private boolean disposeInputConsumer(InputConsumerImpl consumer) {
|
if (consumer != null) {
|
consumer.disposeChannelsLw();
|
consumer.hide(mInputTransaction);
|
return true;
|
}
|
return false;
|
}
|
|
InputConsumerImpl getInputConsumer(String name) {
|
return mInputConsumers.get(name);
|
}
|
|
void layoutInputConsumers(int dw, int dh) {
|
try {
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "layoutInputConsumer");
|
for (int i = mInputConsumers.size() - 1; i >= 0; i--) {
|
mInputConsumers.valueAt(i).layout(mInputTransaction, dw, dh);
|
}
|
} finally {
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
}
|
}
|
|
// The visibility of the input consumers is recomputed each time we
|
// update the input windows. We use a model where consumers begin invisible
|
// (set so by this function) and must meet some condition for visibility on each update.
|
void resetInputConsumers(SurfaceControl.Transaction t) {
|
for (int i = mInputConsumers.size() - 1; i >= 0; i--) {
|
mInputConsumers.valueAt(i).hide(t);
|
}
|
}
|
|
WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
|
InputEventReceiver.Factory inputEventReceiverFactory) {
|
if (mInputConsumers.containsKey(name)) {
|
throw new IllegalStateException("Existing input consumer found with name: " + name
|
+ ", display: " + mDisplayId);
|
}
|
final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
|
this, looper, name, inputEventReceiverFactory, Process.myPid(),
|
UserHandle.SYSTEM, mDisplayId);
|
addInputConsumer(name, consumer);
|
return consumer;
|
}
|
|
void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
|
UserHandle clientUser) {
|
if (mInputConsumers.containsKey(name)) {
|
throw new IllegalStateException("Existing input consumer found with name: " + name
|
+ ", display: " + mDisplayId);
|
}
|
|
final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
|
inputChannel, clientPid, clientUser, mDisplayId);
|
switch (name) {
|
case INPUT_CONSUMER_WALLPAPER:
|
consumer.mWindowHandle.hasWallpaper = true;
|
break;
|
case INPUT_CONSUMER_PIP:
|
// The touchable region of the Pip input window is cropped to the bounds of the
|
// stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through
|
consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL;
|
break;
|
}
|
addInputConsumer(name, consumer);
|
}
|
|
|
void populateInputWindowHandle(final InputWindowHandle inputWindowHandle,
|
final WindowState child, int flags, final int type, final boolean isVisible,
|
final boolean hasFocus, final boolean hasWallpaper) {
|
// Add a window to our list of input windows.
|
inputWindowHandle.name = child.toString();
|
flags = child.getSurfaceTouchableRegion(inputWindowHandle, flags);
|
inputWindowHandle.layoutParamsFlags = flags;
|
inputWindowHandle.layoutParamsType = type;
|
inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
|
inputWindowHandle.visible = isVisible;
|
inputWindowHandle.canReceiveKeys = child.canReceiveKeys();
|
inputWindowHandle.hasFocus = hasFocus;
|
inputWindowHandle.hasWallpaper = hasWallpaper;
|
inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false;
|
inputWindowHandle.layer = child.mLayer;
|
inputWindowHandle.ownerPid = child.mSession.mPid;
|
inputWindowHandle.ownerUid = child.mSession.mUid;
|
inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
|
inputWindowHandle.displayId = child.getDisplayId();
|
|
final Rect frame = child.getFrameLw();
|
inputWindowHandle.frameLeft = frame.left;
|
inputWindowHandle.frameTop = frame.top;
|
inputWindowHandle.frameRight = frame.right;
|
inputWindowHandle.frameBottom = frame.bottom;
|
|
// Surface insets are hardcoded to be the same in all directions
|
// and we could probably deprecate the "left/right/top/bottom" concept.
|
// we avoid reintroducing this concept by just choosing one of them here.
|
inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
|
|
if (child.mGlobalScale != 1) {
|
// If we are scaling the window, input coordinates need
|
// to be inversely scaled to map from what is on screen
|
// to what is actually being touched in the UI.
|
inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale;
|
} else {
|
inputWindowHandle.scaleFactor = 1;
|
}
|
|
if (DEBUG_INPUT) {
|
Slog.d(TAG_WM, "addInputWindowHandle: "
|
+ child + ", " + inputWindowHandle);
|
}
|
|
if (hasFocus) {
|
mFocusedInputWindowHandle = inputWindowHandle;
|
}
|
}
|
|
void setUpdateInputWindowsNeededLw() {
|
mUpdateInputWindowsNeeded = true;
|
}
|
|
/* Updates the cached window information provided to the input dispatcher. */
|
void updateInputWindowsLw(boolean force) {
|
if (!force && !mUpdateInputWindowsNeeded) {
|
return;
|
}
|
scheduleUpdateInputWindows();
|
}
|
|
private void scheduleUpdateInputWindows() {
|
if (mDisplayRemoved) {
|
return;
|
}
|
|
if (!mUpdateInputWindowsPending) {
|
mUpdateInputWindowsPending = true;
|
mHandler.post(mUpdateInputWindows);
|
}
|
}
|
|
void updateInputWindowsImmediately() {
|
if (mUpdateInputWindowsPending) {
|
mApplyImmediately = true;
|
mUpdateInputWindows.run();
|
mApplyImmediately = false;
|
}
|
}
|
|
/* Called when the current input focus changes.
|
* Layer assignment is assumed to be complete by the time this is called.
|
*/
|
public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
|
if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) {
|
Slog.d(TAG_WM, "Input focus has changed to " + newWindow);
|
}
|
|
if (newWindow != mInputFocus) {
|
if (newWindow != null && newWindow.canReceiveKeys()) {
|
// Displaying a window implicitly causes dispatching to be unpaused.
|
// This is to protect against bugs if someone pauses dispatching but
|
// forgets to resume.
|
newWindow.mToken.paused = false;
|
}
|
|
mInputFocus = newWindow;
|
setUpdateInputWindowsNeededLw();
|
|
if (updateInputWindows) {
|
updateInputWindowsLw(false /*force*/);
|
}
|
}
|
}
|
|
public void setFocusedAppLw(AppWindowToken newApp) {
|
// Focused app has changed.
|
if (newApp == null) {
|
mService.mInputManager.setFocusedApplication(mDisplayId, null);
|
} else {
|
final InputApplicationHandle handle = newApp.mInputApplicationHandle;
|
handle.name = newApp.toString();
|
handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos;
|
|
mService.mInputManager.setFocusedApplication(mDisplayId, handle);
|
}
|
}
|
|
public void pauseDispatchingLw(WindowToken window) {
|
if (! window.paused) {
|
if (DEBUG_INPUT) {
|
Slog.v(TAG_WM, "Pausing WindowToken " + window);
|
}
|
|
window.paused = true;
|
updateInputWindowsLw(true /*force*/);
|
}
|
}
|
|
public void resumeDispatchingLw(WindowToken window) {
|
if (window.paused) {
|
if (DEBUG_INPUT) {
|
Slog.v(TAG_WM, "Resuming WindowToken " + window);
|
}
|
|
window.paused = false;
|
updateInputWindowsLw(true /*force*/);
|
}
|
}
|
|
void dump(PrintWriter pw, String prefix) {
|
final Set<String> inputConsumerKeys = mInputConsumers.keySet();
|
if (!inputConsumerKeys.isEmpty()) {
|
pw.println(prefix + "InputConsumers:");
|
for (String key : inputConsumerKeys) {
|
mInputConsumers.get(key).dump(pw, key, prefix);
|
}
|
}
|
}
|
|
private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> {
|
InputConsumerImpl navInputConsumer;
|
InputConsumerImpl pipInputConsumer;
|
InputConsumerImpl wallpaperInputConsumer;
|
InputConsumerImpl recentsAnimationInputConsumer;
|
|
private boolean mAddInputConsumerHandle;
|
private boolean mAddPipInputConsumerHandle;
|
private boolean mAddWallpaperInputConsumerHandle;
|
private boolean mAddRecentsAnimationInputConsumerHandle;
|
|
boolean inDrag;
|
WallpaperController wallpaperController;
|
|
// An invalid window handle that tells SurfaceFlinger not update the input info.
|
final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
|
|
private void updateInputWindows(boolean inDrag) {
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
|
|
navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION);
|
pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
|
wallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
|
recentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
|
|
mAddInputConsumerHandle = navInputConsumer != null;
|
mAddPipInputConsumerHandle = pipInputConsumer != null;
|
mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
|
mAddRecentsAnimationInputConsumerHandle = recentsAnimationInputConsumer != null;
|
|
mTmpRect.setEmpty();
|
mDisableWallpaperTouchEvents = false;
|
this.inDrag = inDrag;
|
wallpaperController = mDisplayContent.mWallpaperController;
|
|
resetInputConsumers(mInputTransaction);
|
|
mDisplayContent.forAllWindows(this,
|
true /* traverseTopToBottom */);
|
|
if (mAddWallpaperInputConsumerHandle) {
|
wallpaperInputConsumer.show(mInputTransaction, 0);
|
}
|
|
if (mApplyImmediately) {
|
mInputTransaction.apply();
|
} else {
|
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
|
mDisplayContent.scheduleAnimation();
|
}
|
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
}
|
|
@Override
|
public void accept(WindowState w) {
|
final InputChannel inputChannel = w.mInputChannel;
|
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
|
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
|
|| w.cantReceiveTouchInput()) {
|
if (w.mWinAnimator.hasSurface()) {
|
mInputTransaction.setInputWindowInfo(
|
w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
|
}
|
// Skip this window because it cannot possibly receive input.
|
return;
|
}
|
|
final int flags = w.mAttrs.flags;
|
final int privateFlags = w.mAttrs.privateFlags;
|
final int type = w.mAttrs.type;
|
final boolean hasFocus = w.isFocused();
|
final boolean isVisible = w.isVisibleLw();
|
|
if (mAddRecentsAnimationInputConsumerHandle) {
|
final RecentsAnimationController recentsAnimationController =
|
mService.getRecentsAnimationController();
|
if (recentsAnimationController != null
|
&& recentsAnimationController.shouldApplyInputConsumer(w.mAppToken)) {
|
if (recentsAnimationController.updateInputConsumerForApp(
|
recentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
|
recentsAnimationInputConsumer.show(mInputTransaction, w);
|
mAddRecentsAnimationInputConsumerHandle = false;
|
}
|
}
|
}
|
|
if (w.inPinnedWindowingMode()) {
|
if (mAddPipInputConsumerHandle) {
|
// Update the bounds of the Pip input consumer to match the window bounds.
|
w.getBounds(mTmpRect);
|
pipInputConsumer.layout(mInputTransaction, mTmpRect);
|
|
// The touchable region is relative to the surface top-left
|
mTmpRect.offsetTo(0, 0);
|
pipInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
|
pipInputConsumer.show(mInputTransaction, w);
|
mAddPipInputConsumerHandle = false;
|
}
|
}
|
|
if (mAddInputConsumerHandle
|
&& inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
|
navInputConsumer.show(mInputTransaction, w);
|
mAddInputConsumerHandle = false;
|
}
|
|
if (mAddWallpaperInputConsumerHandle) {
|
if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
|
// Add the wallpaper input consumer above the first visible wallpaper.
|
wallpaperInputConsumer.show(mInputTransaction, w);
|
mAddWallpaperInputConsumerHandle = false;
|
}
|
}
|
|
if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
|
mDisableWallpaperTouchEvents = true;
|
}
|
final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
|
&& (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
|
&& !mDisableWallpaperTouchEvents;
|
|
// If there's a drag in progress and 'child' is a potential drop target,
|
// make sure it's been told about the drag
|
if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
|
mService.mDragDropController.sendDragStartedIfNeededLocked(w);
|
}
|
|
populateInputWindowHandle(
|
inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
|
|
if (w.mWinAnimator.hasSurface()) {
|
mInputTransaction.setInputWindowInfo(
|
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
|
}
|
}
|
}
|
}
|