InputMonitor.java revision 9cac3b4d4629f10423e293a77f3c2184ba867817
1/*
2 * Copyright (C) 2010 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
17package com.android.server.wm;
18
19import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
20import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
21import static com.android.server.wm.WindowManagerService.DEBUG_INPUT;
22
23import android.app.ActivityManagerNative;
24import android.graphics.Rect;
25import android.os.RemoteException;
26import android.util.Log;
27import android.util.Slog;
28import android.view.Display;
29import android.view.InputChannel;
30import android.view.KeyEvent;
31import android.view.WindowManager;
32
33import com.android.server.input.InputApplicationHandle;
34import com.android.server.input.InputManagerService;
35import com.android.server.input.InputWindowHandle;
36
37import java.util.Arrays;
38
39final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
40    private final WindowManagerService mService;
41
42    // Current window with input focus for keys and other non-touch events.  May be null.
43    private WindowState mInputFocus;
44
45    // When true, prevents input dispatch from proceeding until set to false again.
46    private boolean mInputDispatchFrozen;
47
48    // When true, input dispatch proceeds normally.  Otherwise all events are dropped.
49    // Initially false, so that input does not get dispatched until boot is finished at
50    // which point the ActivityManager will enable dispatching.
51    private boolean mInputDispatchEnabled;
52
53    // When true, need to call updateInputWindowsLw().
54    private boolean mUpdateInputWindowsNeeded = true;
55
56    // Array of window handles to provide to the input dispatcher.
57    private InputWindowHandle[] mInputWindowHandles;
58    private int mInputWindowHandleCount;
59
60    // Set to true when the first input device configuration change notification
61    // is received to indicate that the input devices are ready.
62    private final Object mInputDevicesReadyMonitor = new Object();
63    private boolean mInputDevicesReady;
64
65    public InputMonitor(WindowManagerService service) {
66        mService = service;
67    }
68
69    /* Notifies the window manager about a broken input channel.
70     *
71     * Called by the InputManager.
72     */
73    @Override
74    public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
75        if (inputWindowHandle == null) {
76            return;
77        }
78
79        synchronized (mService.mWindowMap) {
80            WindowState windowState = (WindowState) inputWindowHandle.windowState;
81            if (windowState != null) {
82                Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
83                mService.removeWindowLocked(windowState);
84            }
85        }
86    }
87
88    /* Notifies the window manager about an application that is not responding.
89     * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
90     *
91     * Called by the InputManager.
92     */
93    @Override
94    public long notifyANR(InputApplicationHandle inputApplicationHandle,
95            InputWindowHandle inputWindowHandle, String reason) {
96        AppWindowToken appWindowToken = null;
97        WindowState windowState = null;
98        boolean aboveSystem = false;
99        synchronized (mService.mWindowMap) {
100            if (inputWindowHandle != null) {
101                windowState = (WindowState) inputWindowHandle.windowState;
102                if (windowState != null) {
103                    appWindowToken = windowState.mAppToken;
104                }
105            }
106            if (appWindowToken == null && inputApplicationHandle != null) {
107                appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken;
108            }
109
110            if (windowState != null) {
111                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
112                        + "sending to " + windowState.mAttrs.getTitle()
113                        + ".  Reason: " + reason);
114                // Figure out whether this window is layered above system windows.
115                // We need to do this here to help the activity manager know how to
116                // layer its ANR dialog.
117                int systemAlertLayer = mService.mPolicy.windowTypeToLayerLw(
118                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
119                aboveSystem = windowState.mBaseLayer > systemAlertLayer;
120            } else if (appWindowToken != null) {
121                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
122                        + "sending to application " + appWindowToken.stringName
123                        + ".  Reason: " + reason);
124            } else {
125                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
126                        + ".  Reason: " + reason);
127            }
128
129            mService.saveANRStateLocked(appWindowToken, windowState, reason);
130        }
131
132        if (appWindowToken != null && appWindowToken.appToken != null) {
133            try {
134                // Notify the activity manager about the timeout and let it decide whether
135                // to abort dispatching or keep waiting.
136                boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason);
137                if (! abort) {
138                    // The activity manager declined to abort dispatching.
139                    // Wait a bit longer and timeout again later.
140                    return appWindowToken.inputDispatchingTimeoutNanos;
141                }
142            } catch (RemoteException ex) {
143            }
144        } else if (windowState != null) {
145            try {
146                // Notify the activity manager about the timeout and let it decide whether
147                // to abort dispatching or keep waiting.
148                long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut(
149                        windowState.mSession.mPid, aboveSystem, reason);
150                if (timeout >= 0) {
151                    // The activity manager declined to abort dispatching.
152                    // Wait a bit longer and timeout again later.
153                    return timeout * 1000000L; // nanoseconds
154                }
155            } catch (RemoteException ex) {
156            }
157        }
158        return 0; // abort dispatching
159    }
160
161    private void addInputWindowHandleLw(final InputWindowHandle windowHandle) {
162        if (mInputWindowHandles == null) {
163            mInputWindowHandles = new InputWindowHandle[16];
164        }
165        if (mInputWindowHandleCount >= mInputWindowHandles.length) {
166            mInputWindowHandles = Arrays.copyOf(mInputWindowHandles,
167                    mInputWindowHandleCount * 2);
168        }
169        mInputWindowHandles[mInputWindowHandleCount++] = windowHandle;
170    }
171
172    private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
173            final WindowState child, int flags, final int type, final boolean isVisible,
174            final boolean hasFocus, final boolean hasWallpaper, DisplayContent displayContent) {
175        // Add a window to our list of input windows.
176        inputWindowHandle.name = child.toString();
177        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags, this);
178        inputWindowHandle.layoutParamsFlags = flags;
179        inputWindowHandle.layoutParamsType = type;
180        inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
181        inputWindowHandle.visible = isVisible;
182        inputWindowHandle.canReceiveKeys = child.canReceiveKeys();
183        inputWindowHandle.hasFocus = hasFocus;
184        inputWindowHandle.hasWallpaper = hasWallpaper;
185        inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false;
186        inputWindowHandle.layer = child.mLayer;
187        inputWindowHandle.ownerPid = child.mSession.mPid;
188        inputWindowHandle.ownerUid = child.mSession.mUid;
189        inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
190
191        final Rect frame = child.mFrame;
192        inputWindowHandle.frameLeft = frame.left;
193        inputWindowHandle.frameTop = frame.top;
194        inputWindowHandle.frameRight = frame.right;
195        inputWindowHandle.frameBottom = frame.bottom;
196        if (child.mAttrs.type == TYPE_DOCK_DIVIDER) {
197            // We need to determine if the divider is horizontal or vertical and adjust its handle
198            // frame accordingly.
199            int adjustment = displayContent.mDividerControllerLocked.getWidthAdjustment();
200            if (inputWindowHandle.frameRight - inputWindowHandle.frameLeft >
201                    inputWindowHandle.frameTop - inputWindowHandle.frameBottom) {
202                // Horizontal divider.
203                inputWindowHandle.frameTop -= adjustment;
204                inputWindowHandle.frameBottom += adjustment;
205            } else {
206                inputWindowHandle.frameLeft -= adjustment;
207                inputWindowHandle.frameRight += adjustment;
208            }
209        }
210
211        if (child.mGlobalScale != 1) {
212            // If we are scaling the window, input coordinates need
213            // to be inversely scaled to map from what is on screen
214            // to what is actually being touched in the UI.
215            inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale;
216        } else {
217            inputWindowHandle.scaleFactor = 1;
218        }
219
220        if (DEBUG_INPUT) {
221            Slog.d(WindowManagerService.TAG, "addInputWindowHandle: " + inputWindowHandle);
222        }
223        addInputWindowHandleLw(inputWindowHandle);
224    }
225
226    private void clearInputWindowHandlesLw() {
227        while (mInputWindowHandleCount != 0) {
228            mInputWindowHandles[--mInputWindowHandleCount] = null;
229        }
230    }
231
232    public void setUpdateInputWindowsNeededLw() {
233        mUpdateInputWindowsNeeded = true;
234    }
235
236    /* Updates the cached window information provided to the input dispatcher. */
237    public void updateInputWindowsLw(boolean force) {
238        if (!force && !mUpdateInputWindowsNeeded) {
239            return;
240        }
241        mUpdateInputWindowsNeeded = false;
242
243        if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED updateInputWindowsLw");
244
245        // Populate the input window list with information about all of the windows that
246        // could potentially receive input.
247        // As an optimization, we could try to prune the list of windows but this turns
248        // out to be difficult because only the native code knows for sure which window
249        // currently has touch focus.
250        boolean disableWallpaperTouchEvents = false;
251
252        // If there's a drag in flight, provide a pseudowindow to catch drag input
253        final boolean inDrag = (mService.mDragState != null);
254        if (inDrag) {
255            if (WindowManagerService.DEBUG_DRAG) {
256                Log.d(WindowManagerService.TAG, "Inserting drag window");
257            }
258            final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle;
259            if (dragWindowHandle != null) {
260                addInputWindowHandleLw(dragWindowHandle);
261            } else {
262                Slog.w(WindowManagerService.TAG, "Drag is in progress but there is no "
263                        + "drag window handle.");
264            }
265        }
266
267        final boolean inPositioning = (mService.mTaskPositioner != null);
268        if (inPositioning) {
269            if (WindowManagerService.DEBUG_TASK_POSITIONING) {
270                Log.d(WindowManagerService.TAG, "Inserting window handle for repositioning");
271            }
272            final InputWindowHandle dragWindowHandle = mService.mTaskPositioner.mDragWindowHandle;
273            if (dragWindowHandle != null) {
274                addInputWindowHandleLw(dragWindowHandle);
275            } else {
276                Slog.e(WindowManagerService.TAG,
277                        "Repositioning is in progress but there is no drag window handle.");
278            }
279        }
280
281        boolean addInputConsumerHandle = mService.mInputConsumer != null;
282
283        // Add all windows on the default display.
284        final int numDisplays = mService.mDisplayContents.size();
285        final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
286        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
287            final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx);
288            final WindowList windows = displayContent.getWindowList();
289            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
290                final WindowState child = windows.get(winNdx);
291                final InputChannel inputChannel = child.mInputChannel;
292                final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
293                if (inputChannel == null || inputWindowHandle == null || child.mRemoved) {
294                    // Skip this window because it cannot possibly receive input.
295                    continue;
296                }
297                if (addInputConsumerHandle
298                        && inputWindowHandle.layer <= mService.mInputConsumer.mWindowHandle.layer) {
299                    addInputWindowHandleLw(mService.mInputConsumer.mWindowHandle);
300                    addInputConsumerHandle = false;
301                }
302
303                final int flags = child.mAttrs.flags;
304                final int privateFlags = child.mAttrs.privateFlags;
305                final int type = child.mAttrs.type;
306
307                final boolean hasFocus = (child == mInputFocus);
308                final boolean isVisible = child.isVisibleLw();
309                if ((privateFlags
310                        & WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS)
311                            != 0) {
312                    disableWallpaperTouchEvents = true;
313                }
314                final boolean hasWallpaper = wallpaperController.isWallpaperTarget(child)
315                        && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0
316                        && !disableWallpaperTouchEvents;
317                final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
318
319                // If there's a drag in progress and 'child' is a potential drop target,
320                // make sure it's been told about the drag
321                if (inDrag && isVisible && onDefaultDisplay) {
322                    mService.mDragState.sendDragStartedIfNeededLw(child);
323                }
324
325                addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus,
326                        hasWallpaper, displayContent);
327            }
328        }
329
330        // Send windows to native code.
331        mService.mInputManager.setInputWindows(mInputWindowHandles);
332
333        // Clear the list in preparation for the next round.
334        clearInputWindowHandlesLw();
335
336        if (false) Slog.d(WindowManagerService.TAG, "<<<<<<< EXITED updateInputWindowsLw");
337    }
338
339    /* Notifies that the input device configuration has changed. */
340    @Override
341    public void notifyConfigurationChanged() {
342        mService.sendNewConfiguration();
343
344        synchronized (mInputDevicesReadyMonitor) {
345            if (!mInputDevicesReady) {
346                mInputDevicesReady = true;
347                mInputDevicesReadyMonitor.notifyAll();
348            }
349        }
350    }
351
352    /* Waits until the built-in input devices have been configured. */
353    public boolean waitForInputDevicesReady(long timeoutMillis) {
354        synchronized (mInputDevicesReadyMonitor) {
355            if (!mInputDevicesReady) {
356                try {
357                    mInputDevicesReadyMonitor.wait(timeoutMillis);
358                } catch (InterruptedException ex) {
359                }
360            }
361            return mInputDevicesReady;
362        }
363    }
364
365    /* Notifies that the lid switch changed state. */
366    @Override
367    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
368        mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
369    }
370
371    /* Notifies that the camera lens cover state has changed. */
372    @Override
373    public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
374        mService.mPolicy.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
375    }
376
377    /* Provides an opportunity for the window manager policy to intercept early key
378     * processing as soon as the key has been read from the device. */
379    @Override
380    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
381        return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
382    }
383
384    /* Provides an opportunity for the window manager policy to intercept early motion event
385     * processing when the device is in a non-interactive state since these events are normally
386     * dropped. */
387    @Override
388    public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) {
389        return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
390                whenNanos, policyFlags);
391    }
392
393    /* Provides an opportunity for the window manager policy to process a key before
394     * ordinary dispatch. */
395    @Override
396    public long interceptKeyBeforeDispatching(
397            InputWindowHandle focus, KeyEvent event, int policyFlags) {
398        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
399        return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
400    }
401
402    /* Provides an opportunity for the window manager policy to process a key that
403     * the application did not handle. */
404    @Override
405    public KeyEvent dispatchUnhandledKey(
406            InputWindowHandle focus, KeyEvent event, int policyFlags) {
407        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
408        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
409    }
410
411    /* Callback to get pointer layer. */
412    @Override
413    public int getPointerLayer() {
414        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_POINTER)
415                * WindowManagerService.TYPE_LAYER_MULTIPLIER
416                + WindowManagerService.TYPE_LAYER_OFFSET;
417    }
418
419    /* Called when the current input focus changes.
420     * Layer assignment is assumed to be complete by the time this is called.
421     */
422    public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
423        if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) {
424            Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow);
425        }
426
427        if (newWindow != mInputFocus) {
428            if (newWindow != null && newWindow.canReceiveKeys()) {
429                // Displaying a window implicitly causes dispatching to be unpaused.
430                // This is to protect against bugs if someone pauses dispatching but
431                // forgets to resume.
432                newWindow.mToken.paused = false;
433            }
434
435            mInputFocus = newWindow;
436            setUpdateInputWindowsNeededLw();
437
438            if (updateInputWindows) {
439                updateInputWindowsLw(false /*force*/);
440            }
441        }
442    }
443
444    public void setFocusedAppLw(AppWindowToken newApp) {
445        // Focused app has changed.
446        if (newApp == null) {
447            mService.mInputManager.setFocusedApplication(null);
448        } else {
449            final InputApplicationHandle handle = newApp.mInputApplicationHandle;
450            handle.name = newApp.toString();
451            handle.dispatchingTimeoutNanos = newApp.inputDispatchingTimeoutNanos;
452
453            mService.mInputManager.setFocusedApplication(handle);
454        }
455    }
456
457    public void pauseDispatchingLw(WindowToken window) {
458        if (! window.paused) {
459            if (DEBUG_INPUT) {
460                Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
461            }
462
463            window.paused = true;
464            updateInputWindowsLw(true /*force*/);
465        }
466    }
467
468    public void resumeDispatchingLw(WindowToken window) {
469        if (window.paused) {
470            if (DEBUG_INPUT) {
471                Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
472            }
473
474            window.paused = false;
475            updateInputWindowsLw(true /*force*/);
476        }
477    }
478
479    public void freezeInputDispatchingLw() {
480        if (! mInputDispatchFrozen) {
481            if (DEBUG_INPUT) {
482                Slog.v(WindowManagerService.TAG, "Freezing input dispatching");
483            }
484
485            mInputDispatchFrozen = true;
486            updateInputDispatchModeLw();
487        }
488    }
489
490    public void thawInputDispatchingLw() {
491        if (mInputDispatchFrozen) {
492            if (DEBUG_INPUT) {
493                Slog.v(WindowManagerService.TAG, "Thawing input dispatching");
494            }
495
496            mInputDispatchFrozen = false;
497            updateInputDispatchModeLw();
498        }
499    }
500
501    public void setEventDispatchingLw(boolean enabled) {
502        if (mInputDispatchEnabled != enabled) {
503            if (DEBUG_INPUT) {
504                Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled);
505            }
506
507            mInputDispatchEnabled = enabled;
508            updateInputDispatchModeLw();
509        }
510    }
511
512    private void updateInputDispatchModeLw() {
513        mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
514    }
515}
516