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