From 95b38a906f5f146d2d40d5ca2717c027692834eb Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Fri, 20 Jul 2018 18:56:12 +0800 Subject: [PATCH] Fix WM input limitations on secondary displays (1/N) - One DisplayContent include one InputMonitor. - For customized inputconsumers, they can be handled by DisplayContent. - Extract InputManagerCallback to communicated InputManagerService. - All windows need pass displayId to InputWindowHandle. Bug: 111363643 Test: atest WindowManagerSmokeTest Test: atest DisplayContentTests Change-Id: Iaaca7d135a11c55a78bcf1f81918599b3d160106 --- .../server/policy/PhoneWindowManager.java | 3 +- .../server/policy/WindowManagerPolicy.java | 2 +- .../wm/AppWindowContainerController.java | 4 +- .../com/android/server/wm/AppWindowToken.java | 6 +- .../com/android/server/wm/DisplayContent.java | 20 +- .../java/com/android/server/wm/DragState.java | 4 +- .../android/server/wm/InputConsumerImpl.java | 9 +- .../server/wm/InputManagerCallback.java | 278 ++++++++++++++++ .../com/android/server/wm/InputMonitor.java | 301 ++---------------- .../server/wm/RecentsAnimationController.java | 17 +- .../server/wm/RootWindowContainer.java | 19 +- .../com/android/server/wm/TaskPositioner.java | 1 + .../server/wm/TaskPositioningController.java | 9 +- .../server/wm/WindowManagerService.java | 64 ++-- .../com/android/server/wm/WindowState.java | 4 +- .../java/com/android/server/SystemServer.java | 2 +- 16 files changed, 415 insertions(+), 328 deletions(-) create mode 100644 services/core/java/com/android/server/wm/InputManagerCallback.java diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b6222bb89afe8..8ef1196bf6251 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4669,7 +4669,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) { mInputConsumer = mWindowManagerFuncs.createInputConsumer(mHandler.getLooper(), INPUT_CONSUMER_NAVIGATION, - (channel, looper) -> new HideNavInputEventReceiver(channel, looper)); + (channel, looper) -> new HideNavInputEventReceiver(channel, looper), + displayFrames.mDisplayId); // As long as mInputConsumer is active, hover events are not dispatched to the app // and the pointer icon is likely to become stale. Hide it to avoid confusion. InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 45ce36b671066..00603285336db 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -525,7 +525,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * Add a input consumer which will consume all input events going to any window below it. */ public InputConsumer createInputConsumer(Looper looper, String name, - InputEventReceiver.Factory inputEventReceiverFactory); + InputEventReceiver.Factory inputEventReceiverFactory, int displayId); /** * Returns a code that describes the current state of the lid switch. diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 4f15c5d149fff..36280dd98180c 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -648,7 +648,7 @@ public class AppWindowContainerController public void pauseKeyDispatching() { synchronized (mWindowMap) { if (mContainer != null) { - mService.mInputMonitor.pauseDispatchingLw(mContainer); + mContainer.getDisplayContent().getInputMonitor().pauseDispatchingLw(mContainer); } } } @@ -656,7 +656,7 @@ public class AppWindowContainerController public void resumeKeyDispatching() { synchronized (mWindowMap) { if (mContainer != null) { - mService.mInputMonitor.resumeDispatchingLw(mContainer); + mContainer.getDisplayContent().getInputMonitor().resumeDispatchingLw(mContainer); } } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index d9ddf9f9b3a45..4b8b6074a480c 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -449,13 +449,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested); if (changed) { - mService.mInputMonitor.setUpdateInputWindowsNeededLw(); + getDisplayContent().getInputMonitor().setUpdateInputWindowsNeededLw(); if (performLayout) { mService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); mService.mWindowPlacerLocked.performSurfacePlacement(); } - mService.mInputMonitor.updateInputWindowsLw(false /*force*/); + getDisplayContent().getInputMonitor().updateInputWindowsLw(false /*force*/); } } @@ -677,7 +677,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this); mService.mFocusedApp = null; mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); - mService.mInputMonitor.setFocusedAppLw(null); + getDisplayContent().getInputMonitor().setFocusedAppLw(null); } if (!delayed) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 31c0bdd5570fe..aa52b449b4b4a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -401,6 +401,8 @@ class DisplayContent extends WindowContainer mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final AppWindowToken atoken = w.mAppToken; @@ -798,6 +800,8 @@ class DisplayContent extends WindowContainer systemAlertLayer; + } else if (appWindowToken != null) { + Slog.i(TAG_WM, "Input event dispatching timed out " + + "sending to application " + appWindowToken.stringName + + ". Reason: " + reason); + } else { + Slog.i(TAG_WM, "Input event dispatching timed out " + + ". Reason: " + reason); + } + + mService.saveANRStateLocked(appWindowToken, windowState, reason); + } + + // All the calls below need to happen without the WM lock held since they call into AM. + mService.mAmInternal.saveANRState(reason); + + if (appWindowToken != null && appWindowToken.appToken != null) { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + final AppWindowContainerController controller = appWindowToken.getController(); + final boolean abort = controller != null + && controller.keyDispatchingTimedOut(reason, + (windowState != null) ? windowState.mSession.mPid : -1); + if (!abort) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return appWindowToken.mInputDispatchingTimeoutNanos; + } + } else if (windowState != null) { + try { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + long timeout = ActivityManager.getService().inputDispatchingTimedOut( + windowState.mSession.mPid, aboveSystem, reason); + if (timeout >= 0) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return timeout * 1000000L; // nanoseconds + } + } catch (RemoteException ex) { + } + } + return 0; // abort dispatching + } + + /** 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. + mService.sendNewConfiguration(DEFAULT_DISPLAY); + + 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); + } + + /** + * Provides an opportunity for the window manager policy to intercept early motion event + * processing when the device is in a non-interactive state since these events are normally + * dropped. + */ + @Override + public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive( + whenNanos, policyFlags); + } + + /** + * Provides an opportunity for the window manager policy to process a key before + * ordinary dispatch. + */ + @Override + public long interceptKeyBeforeDispatching( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); + } + + /** + * Provides an opportunity for the window manager policy to process a key that + * the application did not handle. + */ + @Override + public KeyEvent dispatchUnhandledKey( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mService.mPolicy.dispatchUnhandledKey(windowState, 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; + } + + /** 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); + } + } +} diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8ab46518ed4dc..d53a21b962508 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.view.Display.DEFAULT_DISPLAY; 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; @@ -24,7 +23,6 @@ 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_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; @@ -33,13 +31,10 @@ 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.app.ActivityManager; import android.graphics.Rect; -import android.os.Debug; import android.os.IBinder; import android.os.Looper; import android.os.Process; -import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; import android.util.ArrayMap; @@ -47,11 +42,8 @@ import android.util.Log; import android.util.Slog; import android.view.InputChannel; import android.view.InputEventReceiver; -import android.view.KeyEvent; -import android.view.WindowManager; import com.android.server.input.InputApplicationHandle; -import com.android.server.input.InputManagerService; import com.android.server.input.InputWindowHandle; import com.android.server.policy.WindowManagerPolicy; @@ -60,23 +52,12 @@ import java.util.Arrays; import java.util.Set; import java.util.function.Consumer; -final class InputMonitor implements InputManagerService.WindowManagerCallbacks { +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, 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; - // When true, need to call updateInputWindowsLw(). private boolean mUpdateInputWindowsNeeded = true; @@ -85,19 +66,12 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { private int mInputWindowHandleCount; private InputWindowHandle mFocusedInputWindowHandle; - private boolean mAddInputConsumerHandle; - private boolean mAddPipInputConsumerHandle; - private boolean mAddWallpaperInputConsumerHandle; - private boolean mAddRecentsAnimationInputConsumerHandle; private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer(); - // 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; + private int mDisplayId; /** * The set of input consumer added to the window manager by name, which consumes input events @@ -113,8 +87,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor, Looper looper, String name, InputEventReceiver.Factory inputEventReceiverFactory, - int clientPid, UserHandle clientUser) { - super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser); + int clientPid, UserHandle clientUser, int displayId) { + super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser, + displayId); mInputMonitor = monitor; mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver( mClientChannel, looper); @@ -130,8 +105,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } } - public InputMonitor(WindowManagerService service) { + public InputMonitor(WindowManagerService service, int displayId) { mService = service; + mDisplayId = displayId; } private void addInputConsumer(String name, InputConsumerImpl consumer) { @@ -156,9 +132,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { return false; } - InputConsumerImpl getInputConsumer(String name, int displayId) { - // TODO(multi-display): Allow input consumers on non-default displays? - return (displayId == DEFAULT_DISPLAY) ? mInputConsumers.get(name) : null; + InputConsumerImpl getInputConsumer(String name) { + return mInputConsumers.get(name); } void layoutInputConsumers(int dw, int dh) { @@ -170,12 +145,12 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name, InputEventReceiver.Factory inputEventReceiverFactory) { if (mInputConsumers.containsKey(name)) { - throw new IllegalStateException("Existing input consumer found with name: " + 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); + UserHandle.SYSTEM, mDisplayId); addInputConsumer(name, consumer); return consumer; } @@ -183,11 +158,12 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { 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); + throw new IllegalStateException("Existing input consumer found with name: " + name + + ", display: " + mDisplayId); } final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name, - inputChannel, clientPid, clientUser); + inputChannel, clientPid, clientUser, mDisplayId); switch (name) { case INPUT_CONSUMER_WALLPAPER: consumer.mWindowHandle.hasWallpaper = true; @@ -201,100 +177,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { addInputConsumer(name, consumer); } - /* Notifies the window manager about a broken input channel. - * - * Called by the InputManager. - */ - @Override - public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { - if (inputWindowHandle == null) { - return; - } - - synchronized (mService.mWindowMap) { - WindowState windowState = (WindowState) inputWindowHandle.windowState; - if (windowState != null) { - Slog.i(TAG_WM, "WINDOW DIED " + windowState); - windowState.removeIfPossible(); - } - } - } - - /* 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, - InputWindowHandle inputWindowHandle, String reason) { - AppWindowToken appWindowToken = null; - WindowState windowState = null; - boolean aboveSystem = false; - synchronized (mService.mWindowMap) { - if (inputWindowHandle != null) { - windowState = (WindowState) inputWindowHandle.windowState; - if (windowState != null) { - appWindowToken = windowState.mAppToken; - } - } - if (appWindowToken == null && inputApplicationHandle != null) { - appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken; - } - - if (windowState != null) { - Slog.i(TAG_WM, "Input event dispatching timed out " - + "sending to " + windowState.mAttrs.getTitle() - + ". Reason: " + reason); - // 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. - int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw( - TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow); - aboveSystem = windowState.mBaseLayer > systemAlertLayer; - } else if (appWindowToken != null) { - Slog.i(TAG_WM, "Input event dispatching timed out " - + "sending to application " + appWindowToken.stringName - + ". Reason: " + reason); - } else { - Slog.i(TAG_WM, "Input event dispatching timed out " - + ". Reason: " + reason); - } - - mService.saveANRStateLocked(appWindowToken, windowState, reason); - } - - // All the calls below need to happen without the WM lock held since they call into AM. - mService.mAmInternal.saveANRState(reason); - - if (appWindowToken != null && appWindowToken.appToken != null) { - // Notify the activity manager about the timeout and let it decide whether - // to abort dispatching or keep waiting. - final AppWindowContainerController controller = appWindowToken.getController(); - final boolean abort = controller != null - && controller.keyDispatchingTimedOut(reason, - (windowState != null) ? windowState.mSession.mPid : -1); - if (!abort) { - // The activity manager declined to abort dispatching. - // Wait a bit longer and timeout again later. - return appWindowToken.mInputDispatchingTimeoutNanos; - } - } else if (windowState != null) { - try { - // Notify the activity manager about the timeout and let it decide whether - // to abort dispatching or keep waiting. - long timeout = ActivityManager.getService().inputDispatchingTimedOut( - windowState.mSession.mPid, aboveSystem, reason); - if (timeout >= 0) { - // The activity manager declined to abort dispatching. - // Wait a bit longer and timeout again later. - return timeout * 1000000L; // nanoseconds - } - } catch (RemoteException ex) { - } - } - return 0; // abort dispatching - } private void addInputWindowHandle(final InputWindowHandle windowHandle) { if (mInputWindowHandles == null) { @@ -325,6 +207,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { 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; @@ -414,87 +297,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw"); } - /* 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. - mService.sendNewConfiguration(DEFAULT_DISPLAY); - - synchronized (mInputDevicesReadyMonitor) { - if (!mInputDevicesReady) { - mInputDevicesReady = true; - mInputDevicesReadyMonitor.notifyAll(); - } - } - } - - /* 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; - } - } - - /* 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); - } - - /* Provides an opportunity for the window manager policy to intercept early motion event - * processing when the device is in a non-interactive state since these events are normally - * dropped. */ - @Override - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { - return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive( - whenNanos, policyFlags); - } - - /* Provides an opportunity for the window manager policy to process a key before - * ordinary dispatch. */ - @Override - public long interceptKeyBeforeDispatching( - InputWindowHandle focus, KeyEvent event, int policyFlags) { - WindowState windowState = focus != null ? (WindowState) focus.windowState : null; - return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); - } - - /* Provides an opportunity for the window manager policy to process a key that - * the application did not handle. */ - @Override - public KeyEvent dispatchUnhandledKey( - InputWindowHandle focus, KeyEvent event, int policyFlags) { - WindowState windowState = focus != null ? (WindowState) focus.windowState : null; - return mService.mPolicy.dispatchUnhandledKey(windowState, 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; - } - /* Called when the current input focus changes. * Layer assignment is assumed to be complete by the time this is called. */ @@ -555,52 +357,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } } - public void freezeInputDispatchingLw() { - if (!mInputDispatchFrozen) { - if (DEBUG_INPUT) { - Slog.v(TAG_WM, "Freezing input dispatching"); - } - - mInputDispatchFrozen = true; - - if (DEBUG_INPUT || true) { - 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); - } final Set inputConsumerKeys = mInputConsumers.keySet(); if (!inputConsumerKeys.isEmpty()) { pw.println(prefix + "InputConsumers:"); @@ -611,40 +368,46 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } private final class UpdateInputForAllWindowsConsumer implements Consumer { - InputConsumerImpl navInputConsumer; InputConsumerImpl pipInputConsumer; InputConsumerImpl wallpaperInputConsumer; InputConsumerImpl recentsAnimationInputConsumer; + + private boolean mAddInputConsumerHandle; + private boolean mAddPipInputConsumerHandle; + private boolean mAddWallpaperInputConsumerHandle; + private boolean mAddRecentsAnimationInputConsumerHandle; + boolean inDrag; WallpaperController wallpaperController; private void updateInputWindows(boolean inDrag) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); - // TODO: multi-display - navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY); - pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY); - wallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY); - recentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION, - DEFAULT_DISPLAY); + 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 = mService.mRoot.mWallpaperController; - mService.mRoot.forAllWindows(this, true /* traverseTopToBottom */); + mService.mRoot.getDisplayContent(mDisplayId).forAllWindows(this, + true /* traverseTopToBottom */); if (mAddWallpaperInputConsumerHandle) { // No visible wallpaper found, add the wallpaper input consumer at the end. addInputWindowHandle(wallpaperInputConsumer.mWindowHandle); } // Send windows to native code. + // TODO: Update Input windows and focus by display? mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle); clearInputWindowHandlesLw(); @@ -674,7 +437,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (recentsAnimationController != null && recentsAnimationController.hasInputConsumerForApp(w.mAppToken)) { if (recentsAnimationController.updateInputConsumerForApp( - recentsAnimationInputConsumer, hasFocus)) { + recentsAnimationInputConsumer.mWindowHandle, hasFocus)) { addInputWindowHandle(recentsAnimationInputConsumer.mWindowHandle); mAddRecentsAnimationInputConsumerHandle = false; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 6f5fea960c7ea..3a0cc28172832 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -53,6 +53,7 @@ import com.google.android.collect.Sets; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import com.android.server.input.InputWindowHandle; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.InsetUtils; @@ -201,7 +202,9 @@ public class RecentsAnimationController implements DeathRecipient { } mInputConsumerEnabled = enabled; - mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + final InputMonitor inputMonitor = + mService.mRoot.getDisplayContent(mDisplayId).getInputMonitor(); + inputMonitor.updateInputWindowsLw(true /*force*/); mService.scheduleAnimationLocked(); } } finally { @@ -438,8 +441,10 @@ public class RecentsAnimationController implements DeathRecipient { mCanceled = true; // Clear associated input consumers - mService.mInputMonitor.updateInputWindowsLw(true /*force*/); - mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + final InputMonitor inputMonitor = + mService.mRoot.getDisplayContent(mDisplayId).getInputMonitor(); + inputMonitor.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + inputMonitor.updateInputWindowsLw(true /*force*/); // We have deferred all notifications to the target app as a part of the recents animation, // so if we are actually transitioning there, notify again here @@ -497,7 +502,7 @@ public class RecentsAnimationController implements DeathRecipient { return mInputConsumerEnabled && isAnimatingApp(appToken); } - boolean updateInputConsumerForApp(InputConsumerImpl recentsAnimationInputConsumer, + boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle, boolean hasFocus) { // Update the input consumer touchable region to match the target app main window final WindowState targetAppMainWindow = mTargetAppToken != null @@ -505,8 +510,8 @@ public class RecentsAnimationController implements DeathRecipient { : null; if (targetAppMainWindow != null) { targetAppMainWindow.getBounds(mTmpRect); - recentsAnimationInputConsumer.mWindowHandle.hasFocus = hasFocus; - recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect); + inputWindowHandle.hasFocus = hasFocus; + inputWindowHandle.touchableRegion.set(mTmpRect); return true; } return false; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2be400136f01a..3df4eb7fbf38c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -724,7 +724,9 @@ class RootWindowContainer extends WindowContainer { } // Finally update all input windows now that the window changes have stabilized. - mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + forAllDisplays(dc -> { + dc.getInputMonitor().updateInputWindowsLw(true /*force*/); + }); mService.setHoldScreenLocked(mHoldScreen); if (!mService.mDisplayFrozen) { @@ -784,7 +786,9 @@ class RootWindowContainer extends WindowContainer { } if (updateInputWindowsNeeded) { - mService.mInputMonitor.updateInputWindowsLw(false /*force*/); + forAllDisplays(dc -> { + dc.getInputMonitor().updateInputWindowsLw(false /*force*/); + }); } mService.setFocusTaskRegionLocked(null); if (touchExcludeRegionUpdateDisplays != null) { @@ -1091,4 +1095,15 @@ class RootWindowContainer extends WindowContainer { void scheduleAnimation() { mService.scheduleAnimationLocked(); } + + /** + * For all display at or below this call the callback. + * + * @param callback Callback to be called for every display. + */ + void forAllDisplays(Consumer callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + callback.accept(mChildren.get(i)); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index bd7e61cc34e5a..8effc6bbdd594 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -209,6 +209,7 @@ class TaskPositioner { // Post back to WM to handle clean-ups. We still need the input // event handler for the last finishInputEvent()! mService.mTaskPositioningController.finishTaskPositioning(); + mTask.getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/); } handled = true; } catch (Exception e) { diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java index 7d36650f375ba..9cdc6b75c3b6c 100644 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ b/services/core/java/com/android/server/wm/TaskPositioningController.java @@ -38,7 +38,6 @@ import com.android.server.input.InputWindowHandle; class TaskPositioningController { private final WindowManagerService mService; private final InputManagerService mInputManager; - private final InputMonitor mInputMonitor; private final IActivityTaskManager mActivityManager; private final Handler mHandler; @@ -54,9 +53,8 @@ class TaskPositioningController { } TaskPositioningController(WindowManagerService service, InputManagerService inputManager, - InputMonitor inputMonitor, IActivityTaskManager activityManager, Looper looper) { + IActivityTaskManager activityManager, Looper looper) { mService = service; - mInputMonitor = inputMonitor; mInputManager = inputManager; mActivityManager = activityManager; mHandler = new Handler(looper); @@ -129,7 +127,7 @@ class TaskPositioningController { Display display = displayContent.getDisplay(); mTaskPositioner = TaskPositioner.create(mService); mTaskPositioner.register(displayContent); - mInputMonitor.updateInputWindowsLw(true /*force*/); + displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); // We need to grab the touch focus so that the touch events during the // resizing/scrolling are not sent to the app. 'win' is the main window @@ -145,7 +143,7 @@ class TaskPositioningController { Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); mTaskPositioner.unregister(); mTaskPositioner = null; - mInputMonitor.updateInputWindowsLw(true /*force*/); + displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); return false; } @@ -161,7 +159,6 @@ class TaskPositioningController { if (mTaskPositioner != null) { mTaskPositioner.unregister(); mTaskPositioner = null; - mInputMonitor.updateInputWindowsLw(true /*force*/); } } }); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 06a1968fa18dc..9b792e3f2d138 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -269,6 +269,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { @@ -1073,7 +1075,7 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); mTaskPositioningController = new TaskPositioningController( - this, mInputManager, mInputMonitor, mActivityTaskManager, mH.getLooper()); + this, mInputManager, mActivityTaskManager, mH.getLooper()); mDragDropController = new DragDropController(this, mH.getLooper()); LocalServices.addService(WindowManagerInternal.class, new LocalService()); @@ -1099,9 +1101,8 @@ public class WindowManagerService extends IWindowManager.Stub showEmulatorDisplayOverlayIfNeeded(); } - - public InputMonitor getInputMonitor() { - return mInputMonitor; + public InputManagerCallback getInputManagerCallback() { + return mInputManagerCallback; } @Override @@ -1489,7 +1490,7 @@ public class WindowManagerService extends IWindowManager.Stub res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } - mInputMonitor.setUpdateInputWindowsNeededLw(); + displayContent.getInputMonitor().setUpdateInputWindowsNeededLw(); boolean focusChanged = false; if (win.canReceiveKeys()) { @@ -1509,9 +1510,10 @@ public class WindowManagerService extends IWindowManager.Stub win.getParent().assignChildLayers(); if (focusChanged) { - mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/); + displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus, + false /*updateInputWindows*/); } - mInputMonitor.updateInputWindowsLw(false /*force*/); + displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client " + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5)); @@ -1724,7 +1726,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputMonitor.updateInputWindowsLw(true /*force*/); + dc.getInputMonitor().updateInputWindowsLw(true /*force*/); } void setInputMethodWindowLocked(WindowState win) { @@ -2043,7 +2045,7 @@ public class WindowManagerService extends IWindowManager.Stub try { result = createSurfaceControl(outSurface, result, win, winAnimator); } catch (Exception e) { - mInputMonitor.updateInputWindowsLw(true /*force*/); + win.getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/); Slog.w(TAG_WM, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", @@ -2377,7 +2379,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } - mInputMonitor.updateInputWindowsLw(true /*force*/); + dc.getInputMonitor().updateInputWindowsLw(true /*force*/); } } finally { Binder.restoreCallingIdentity(origId); @@ -2566,7 +2568,7 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { AppWindowToken prev = mFocusedApp; mFocusedApp = newFocus; - mInputMonitor.setFocusedAppLw(newFocus); + mFocusedApp.getDisplayContent().getInputMonitor().setFocusedAppLw(newFocus); setFocusTaskRegionLocked(prev); } @@ -3474,7 +3476,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!"); // Enable input dispatch. - mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled); + mInputManagerCallback.setEventDispatchingLw(mEventDispatchingEnabled); } try { @@ -4421,7 +4423,7 @@ public class WindowManagerService extends IWindowManager.Stub // Input Events and Focus Management // ------------------------------------------------------------- - final InputMonitor mInputMonitor = new InputMonitor(this); + final InputManagerCallback mInputManagerCallback = new InputManagerCallback(this); private boolean mEventDispatchingEnabled; @Override @@ -4433,7 +4435,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mEventDispatchingEnabled = enabled; if (mDisplayEnabled) { - mInputMonitor.setEventDispatchingLw(enabled); + mInputManagerCallback.setEventDispatchingLw(enabled); } } } @@ -4458,7 +4460,7 @@ public class WindowManagerService extends IWindowManager.Stub } public boolean detectSafeMode() { - if (!mInputMonitor.waitForInputDevicesReady( + if (!mInputManagerCallback.waitForInputDevicesReady( INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { Slog.w(TAG_WM, "Devices still not ready after waiting " + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS @@ -5712,7 +5714,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { // If we defer assigning layers, then the caller is responsible for // doing this part. - mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows); + displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus, updateInputWindows); } displayContent.adjustForImeIfNeeded(); @@ -5759,7 +5761,7 @@ public class WindowManagerService extends IWindowManager.Stub // As a result, we only track the display that has initially froze the screen. mFrozenDisplayId = displayContent.getDisplayId(); - mInputMonitor.freezeInputDispatchingLw(); + mInputManagerCallback.freezeInputDispatchingLw(); // Clear the last input window -- that is just used for // clean transitions between IMEs, and if we are freezing @@ -5825,7 +5827,7 @@ public class WindowManagerService extends IWindowManager.Stub final int displayId = mFrozenDisplayId; mFrozenDisplayId = INVALID_DISPLAY; mDisplayFrozen = false; - mInputMonitor.thawInputDispatchingLw(); + mInputManagerCallback.thawInputDispatchingLw(); mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime); StringBuilder sb = new StringBuilder(128); sb.append("Screen frozen for "); @@ -6065,24 +6067,38 @@ public class WindowManagerService extends IWindowManager.Stub @Override public WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name, - InputEventReceiver.Factory inputEventReceiverFactory) { + InputEventReceiver.Factory inputEventReceiverFactory, int displayId) { synchronized (mWindowMap) { - return mInputMonitor.createInputConsumer(looper, name, inputEventReceiverFactory); + DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent != null) { + return displayContent.getInputMonitor().createInputConsumer(looper, name, + inputEventReceiverFactory); + } else { + return null; + } } } @Override public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) { synchronized (mWindowMap) { - mInputMonitor.createInputConsumer(token, name, inputChannel, Binder.getCallingPid(), - Binder.getCallingUserHandle()); + mRoot.forAllDisplays(dc -> { + dc.getInputMonitor().createInputConsumer(token, name, inputChannel, + Binder.getCallingPid(), Binder.getCallingUserHandle()); + }); } } @Override public boolean destroyInputConsumer(String name) { synchronized (mWindowMap) { - return mInputMonitor.destroyInputConsumer(name); + AtomicBoolean retValue = new AtomicBoolean(true); + mRoot.forAllDisplays(dc -> { + if (!dc.getInputMonitor().destroyInputConsumer(name)) { + retValue.set(false); + } + }); + return retValue.get(); } } @@ -6426,7 +6442,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLastWakeLockObscuringWindow="); pw.print(mLastWakeLockObscuringWindow); pw.println(); - mInputMonitor.dump(pw, " "); + mInputManagerCallback.dump(pw, " "); mUnknownAppVisibilityController.dump(pw, " "); mTaskSnapshotController.dump(pw, " "); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 06ffd5e593041..6385229b28c6e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2025,7 +2025,7 @@ class WindowState extends WindowContainer implements WindowManagerP // Set up a replacement input channel since the app is now dead. // We need to catch tapping on the dead window to restart the app. openInputChannel(null); - mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/); return; } @@ -2092,7 +2092,7 @@ class WindowState extends WindowContainer implements WindowManagerP UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); mService.mWindowPlacerLocked.performSurfacePlacement(); if (focusChanged) { - mService.mInputMonitor.updateInputWindowsLw(false /*force*/); + getDisplayContent().getInputMonitor().updateInputWindowsLw(false /*force*/); } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 86c51750d48a2..4017b70c981b9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -903,7 +903,7 @@ public final class SystemServer { } traceBeginAndSlog("StartInputManager"); - inputManager.setWindowManagerCallbacks(wm.getInputMonitor()); + inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback()); inputManager.start(); traceEnd();