Loading core/java/android/view/IDisplayWindowInsetsController.aidl 0 → 100644 +48 −0 Original line number Diff line number Diff line /** * Copyright (C) 2019 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 android.view; import android.view.InsetsSourceControl; import android.view.InsetsState; /** * Singular controller of insets to use when there isn't another obvious controller available. * Specifically, this will take over insets control in multi-window. * @hide */ oneway interface IDisplayWindowInsetsController { /** * @see IWindow#insetsChanged */ void insetsChanged(in InsetsState insetsState); /** * @see IWindow#insetsControlChanged */ void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls); /** * @see IWindow#showInsets */ void showInsets(int types, boolean fromIme); /** * @see IWindow#hideInsets */ void hideInsets(int types, boolean fromIme); } core/java/android/view/IWindowManager.aidl +14 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.os.ParcelFileDescriptor; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowRotationController; Loading @@ -49,6 +50,7 @@ import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.KeyEvent; import android.view.InputEvent; import android.view.InsetsState; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.InputChannel; Loading Loading @@ -711,4 +713,16 @@ interface IWindowManager * @return true if the display was successfully mirrored. */ boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl); /** * When in multi-window mode, the provided displayWindowInsetsController will control insets * animations. */ void setDisplayWindowInsetsController( int displayId, in IDisplayWindowInsetsController displayWindowInsetsController); /** * Called when a remote process modifies insets on a display window container. */ void modifyDisplayWindowInsets(int displayId, in InsetsState state); } packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,7 @@ import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.wm.DisplayImeController; import com.android.systemui.wm.DisplayWindowController; import com.android.systemui.wm.SystemWindows; Loading Loading @@ -321,6 +322,7 @@ public class Dependency { @Inject Lazy<StatusBar> mStatusBar; @Inject Lazy<DisplayWindowController> mDisplayWindowController; @Inject Lazy<SystemWindows> mSystemWindows; @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject public Dependency() { Loading Loading @@ -509,6 +511,7 @@ public class Dependency { mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(DisplayWindowController.class, mDisplayWindowController::get); mProviders.put(SystemWindows.class, mSystemWindows::get); mProviders.put(DisplayImeController.class, mDisplayImeController::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add Loading packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java 0 → 100644 +333 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.systemui.wm; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.res.Configuration; import android.os.Handler; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.systemui.dagger.qualifiers.Main; import java.util.ArrayList; import javax.inject.Inject; import javax.inject.Singleton; /** * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. */ @Singleton public class DisplayImeController implements DisplayWindowController.DisplayWindowListener { private static final String TAG = "DisplayImeController"; static final int ANIMATION_DURATION_SHOW_MS = 275; static final int ANIMATION_DURATION_HIDE_MS = 340; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); private static final int DIRECTION_NONE = 0; private static final int DIRECTION_SHOW = 1; private static final int DIRECTION_HIDE = 2; SystemWindows mSystemWindows; final Handler mHandler; final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); @Inject DisplayImeController(SystemWindows syswin, DisplayWindowController displayController, @Main Handler mainHandler) { mHandler = mainHandler; mSystemWindows = syswin; displayController.addDisplayWindowListener(this); } @Override public void onDisplayAdded(int displayId) { // Add's a system-ui window-manager specifically for ime. This type is special because // WM will defer IME inset handling to it in multi-window scenarious. PerDisplay pd = new PerDisplay(displayId, mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()); try { mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } mImePerDisplay.put(displayId, pd); } @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { PerDisplay pd = mImePerDisplay.get(displayId); if (pd == null) { return; } if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { pd.startAnimation(true); } } @Override public void onDisplayRemoved(int displayId) { try { mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } mImePerDisplay.remove(displayId); } private boolean isImeShowing(int displayId) { PerDisplay pd = mImePerDisplay.get(displayId); if (pd == null) { return false; } final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME); return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); } private void dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImePositionChanged(displayId, imeTop, t); } } } private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop, boolean show, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t); } } } private void dispatchEndPositioning(int displayId, int imeTop, boolean show, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImeEndPositioning(displayId, imeTop, show, t); } } } /** * Adds an {@link ImePositionProcessor} to be called during ime position updates. */ public void addPositionProcessor(ImePositionProcessor processor) { synchronized (mPositionProcessors) { if (mPositionProcessors.contains(processor)) { return; } mPositionProcessors.add(processor); } } /** * Removes an {@link ImePositionProcessor} to be called during ime position updates. */ public void removePositionProcessor(ImePositionProcessor processor) { synchronized (mPositionProcessors) { mPositionProcessors.remove(processor); } } class PerDisplay extends IDisplayWindowInsetsController.Stub { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); InsetsSourceControl mImeSourceControl = null; int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; int mRotation = Surface.ROTATION_0; PerDisplay(int displayId, int initialRotation) { mDisplayId = displayId; mRotation = initialRotation; } @Override public void insetsChanged(InsetsState insetsState) { if (mInsetsState.equals(insetsState)) { return; } mInsetsState.set(insetsState, true /* copySources */); } @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { insetsChanged(insetsState); if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { if (activeControl == null) { continue; } if (activeControl.getType() == InsetsState.ITYPE_IME) { mImeSourceControl = activeControl; } } } } @Override public void showInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } startAnimation(true /* show */); } @Override public void hideInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } startAnimation(false /* show */); } /** * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. */ private void setVisibleDirectly(boolean visible) { mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); try { mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { } } private int imeTop(InsetsSource imeSource, float surfaceOffset) { return imeSource.getFrame().top + (int) surfaceOffset; } private void startAnimation(final boolean show) { final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); if (imeSource == null || mImeSourceControl == null) { return; } if ((mAnimationDirection == DIRECTION_SHOW && show) || (mAnimationDirection == DIRECTION_HIDE && !show)) { return; } if (mAnimationDirection != DIRECTION_NONE) { mAnimation.end(); mAnimationDirection = DIRECTION_NONE; } mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mHandler.post(() -> { final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY; final float endY = show ? defaultY : defaultY + imeSource.getFrame().height(); mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); mAnimation.addUpdateListener(animation -> { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); float value = (float) animation.getAnimatedValue(); t.setPosition(mImeSourceControl.getLeash(), x, value); dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t); t.apply(); t.close(); }); mAnimation.setInterpolator(INTERPOLATOR); mAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setPosition(mImeSourceControl.getLeash(), x, startY); dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY), imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_SHOW) { t.show(mImeSourceControl.getLeash()); } t.apply(); t.close(); } @Override public void onAnimationEnd(Animator animation) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setPosition(mImeSourceControl.getLeash(), x, endY); dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_HIDE) { t.hide(mImeSourceControl.getLeash()); } t.apply(); t.close(); mAnimationDirection = DIRECTION_NONE; mAnimation = null; } }); if (!show) { // When going away, queue up insets change first, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(false /* visible */); } mAnimation.start(); if (show) { // When showing away, queue up insets change last, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(true /* visible */); } }); } } /** * Allows other things to synchronize with the ime position */ public interface ImePositionProcessor { /** * Called when the IME position is starting to animate. */ void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing, SurfaceControl.Transaction t); /** * Called when the ime position changed. This is expected to be a synchronous call on the * animation thread. Operations can be added to the transaction to be applied in sync. */ void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t); /** * Called when the IME position is done animating. */ void onImeEndPositioning(int displayId, int imeTop, boolean showing, SurfaceControl.Transaction t); } } services/core/java/com/android/server/wm/DisplayContent.java +97 −6 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; import android.view.IWindow; import android.view.InputChannel; Loading @@ -218,6 +219,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; Loading Loading @@ -570,6 +572,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ WindowState mInputMethodTarget; InsetsControlTarget mInputMethodControlTarget; /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; Loading Loading @@ -598,6 +602,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final float mWindowCornerRadius; private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>(); RemoteInsetsControlTarget mRemoteInsetsControlTarget = null; private final IBinder.DeathRecipient mRemoteInsetsDeath = () -> { synchronized (mWmService.mGlobalLock) { mRemoteInsetsControlTarget = null; } }; private RootWindowContainer mRootWindowContainer; Loading Loading @@ -1156,6 +1167,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mShellRoots.remove(windowType); } void setRemoteInsetsController(IDisplayWindowInsetsController controller) { if (mRemoteInsetsControlTarget != null) { mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath( mRemoteInsetsDeath, 0); mRemoteInsetsControlTarget = null; } if (controller != null) { try { controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0); mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller); } catch (RemoteException e) { return; } } } /** Changes the display the input window token is housed on to this one. */ void reParentWindowToken(WindowToken token) { final DisplayContent prevDc = token.getDisplayContent(); Loading Loading @@ -3383,6 +3410,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } boolean isImeAttachedToApp() { return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // An activity with override bounds should be letterboxed inside its parent bounds, // so it doesn't fill the screen. && mInputMethodTarget.mActivityRecord.matchParentBounds()); } private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) { if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; Loading @@ -3391,7 +3426,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(false /* setLayoutNeeded */); mInsetsStateController.onImeTargetChanged(target); mInputMethodControlTarget = computeImeControlTarget(); mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget); updateImeParent(); } Loading @@ -3416,11 +3452,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // An activity with override bounds should be letterboxed inside its parent bounds, // so it doesn't fill the screen. && mInputMethodTarget.mActivityRecord.matchParentBounds()) { if (isImeAttachedToApp()) { return mInputMethodTarget.mActivityRecord.getSurfaceControl(); } Loading @@ -3428,6 +3460,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mWindowContainers.getSurfaceControl(); } /** * Computes which control-target the IME should be attached to. */ @VisibleForTesting InsetsControlTarget computeImeControlTarget() { if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) { return mRemoteInsetsControlTarget; } // Otherwise, we just use the ime target return mInputMethodTarget; } void setLayoutNeeded() { if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3)); mLayoutNeeded = true; Loading Loading @@ -6688,4 +6733,50 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Context getDisplayUiContext() { return mDisplayPolicy.getSystemUiContext(); } class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) { mRemoteInsetsController = controller; } void notifyInsetsChanged() { try { mRemoteInsetsController.insetsChanged( getInsetsStateController().getRawInsetsState()); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver inset state change", e); } } @Override public void notifyInsetsControlChanged() { final InsetsStateController stateController = getInsetsStateController(); try { mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(), stateController.getControlsForDispatch(this)); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver inset state change", e); } } @Override public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { try { mRemoteInsetsController.showInsets(types, fromIme); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); } } @Override public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { try { mRemoteInsetsController.hideInsets(types, fromIme); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); } } } } Loading
core/java/android/view/IDisplayWindowInsetsController.aidl 0 → 100644 +48 −0 Original line number Diff line number Diff line /** * Copyright (C) 2019 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 android.view; import android.view.InsetsSourceControl; import android.view.InsetsState; /** * Singular controller of insets to use when there isn't another obvious controller available. * Specifically, this will take over insets control in multi-window. * @hide */ oneway interface IDisplayWindowInsetsController { /** * @see IWindow#insetsChanged */ void insetsChanged(in InsetsState insetsState); /** * @see IWindow#insetsControlChanged */ void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls); /** * @see IWindow#showInsets */ void showInsets(int types, boolean fromIme); /** * @see IWindow#hideInsets */ void hideInsets(int types, boolean fromIme); }
core/java/android/view/IWindowManager.aidl +14 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.os.ParcelFileDescriptor; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowRotationController; Loading @@ -49,6 +50,7 @@ import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.KeyEvent; import android.view.InputEvent; import android.view.InsetsState; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.InputChannel; Loading Loading @@ -711,4 +713,16 @@ interface IWindowManager * @return true if the display was successfully mirrored. */ boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl); /** * When in multi-window mode, the provided displayWindowInsetsController will control insets * animations. */ void setDisplayWindowInsetsController( int displayId, in IDisplayWindowInsetsController displayWindowInsetsController); /** * Called when a remote process modifies insets on a display window container. */ void modifyDisplayWindowInsets(int displayId, in InsetsState state); }
packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,7 @@ import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.wm.DisplayImeController; import com.android.systemui.wm.DisplayWindowController; import com.android.systemui.wm.SystemWindows; Loading Loading @@ -321,6 +322,7 @@ public class Dependency { @Inject Lazy<StatusBar> mStatusBar; @Inject Lazy<DisplayWindowController> mDisplayWindowController; @Inject Lazy<SystemWindows> mSystemWindows; @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject public Dependency() { Loading Loading @@ -509,6 +511,7 @@ public class Dependency { mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(DisplayWindowController.class, mDisplayWindowController::get); mProviders.put(SystemWindows.class, mSystemWindows::get); mProviders.put(DisplayImeController.class, mDisplayImeController::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add Loading
packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java 0 → 100644 +333 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.systemui.wm; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.res.Configuration; import android.os.Handler; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.systemui.dagger.qualifiers.Main; import java.util.ArrayList; import javax.inject.Inject; import javax.inject.Singleton; /** * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. */ @Singleton public class DisplayImeController implements DisplayWindowController.DisplayWindowListener { private static final String TAG = "DisplayImeController"; static final int ANIMATION_DURATION_SHOW_MS = 275; static final int ANIMATION_DURATION_HIDE_MS = 340; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); private static final int DIRECTION_NONE = 0; private static final int DIRECTION_SHOW = 1; private static final int DIRECTION_HIDE = 2; SystemWindows mSystemWindows; final Handler mHandler; final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); @Inject DisplayImeController(SystemWindows syswin, DisplayWindowController displayController, @Main Handler mainHandler) { mHandler = mainHandler; mSystemWindows = syswin; displayController.addDisplayWindowListener(this); } @Override public void onDisplayAdded(int displayId) { // Add's a system-ui window-manager specifically for ime. This type is special because // WM will defer IME inset handling to it in multi-window scenarious. PerDisplay pd = new PerDisplay(displayId, mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()); try { mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } mImePerDisplay.put(displayId, pd); } @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { PerDisplay pd = mImePerDisplay.get(displayId); if (pd == null) { return; } if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { pd.startAnimation(true); } } @Override public void onDisplayRemoved(int displayId) { try { mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } mImePerDisplay.remove(displayId); } private boolean isImeShowing(int displayId) { PerDisplay pd = mImePerDisplay.get(displayId); if (pd == null) { return false; } final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME); return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); } private void dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImePositionChanged(displayId, imeTop, t); } } } private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop, boolean show, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t); } } } private void dispatchEndPositioning(int displayId, int imeTop, boolean show, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { pp.onImeEndPositioning(displayId, imeTop, show, t); } } } /** * Adds an {@link ImePositionProcessor} to be called during ime position updates. */ public void addPositionProcessor(ImePositionProcessor processor) { synchronized (mPositionProcessors) { if (mPositionProcessors.contains(processor)) { return; } mPositionProcessors.add(processor); } } /** * Removes an {@link ImePositionProcessor} to be called during ime position updates. */ public void removePositionProcessor(ImePositionProcessor processor) { synchronized (mPositionProcessors) { mPositionProcessors.remove(processor); } } class PerDisplay extends IDisplayWindowInsetsController.Stub { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); InsetsSourceControl mImeSourceControl = null; int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; int mRotation = Surface.ROTATION_0; PerDisplay(int displayId, int initialRotation) { mDisplayId = displayId; mRotation = initialRotation; } @Override public void insetsChanged(InsetsState insetsState) { if (mInsetsState.equals(insetsState)) { return; } mInsetsState.set(insetsState, true /* copySources */); } @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { insetsChanged(insetsState); if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { if (activeControl == null) { continue; } if (activeControl.getType() == InsetsState.ITYPE_IME) { mImeSourceControl = activeControl; } } } } @Override public void showInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } startAnimation(true /* show */); } @Override public void hideInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } startAnimation(false /* show */); } /** * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. */ private void setVisibleDirectly(boolean visible) { mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); try { mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { } } private int imeTop(InsetsSource imeSource, float surfaceOffset) { return imeSource.getFrame().top + (int) surfaceOffset; } private void startAnimation(final boolean show) { final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); if (imeSource == null || mImeSourceControl == null) { return; } if ((mAnimationDirection == DIRECTION_SHOW && show) || (mAnimationDirection == DIRECTION_HIDE && !show)) { return; } if (mAnimationDirection != DIRECTION_NONE) { mAnimation.end(); mAnimationDirection = DIRECTION_NONE; } mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mHandler.post(() -> { final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY; final float endY = show ? defaultY : defaultY + imeSource.getFrame().height(); mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); mAnimation.addUpdateListener(animation -> { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); float value = (float) animation.getAnimatedValue(); t.setPosition(mImeSourceControl.getLeash(), x, value); dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t); t.apply(); t.close(); }); mAnimation.setInterpolator(INTERPOLATOR); mAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setPosition(mImeSourceControl.getLeash(), x, startY); dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY), imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_SHOW) { t.show(mImeSourceControl.getLeash()); } t.apply(); t.close(); } @Override public void onAnimationEnd(Animator animation) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setPosition(mImeSourceControl.getLeash(), x, endY); dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_HIDE) { t.hide(mImeSourceControl.getLeash()); } t.apply(); t.close(); mAnimationDirection = DIRECTION_NONE; mAnimation = null; } }); if (!show) { // When going away, queue up insets change first, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(false /* visible */); } mAnimation.start(); if (show) { // When showing away, queue up insets change last, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(true /* visible */); } }); } } /** * Allows other things to synchronize with the ime position */ public interface ImePositionProcessor { /** * Called when the IME position is starting to animate. */ void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing, SurfaceControl.Transaction t); /** * Called when the ime position changed. This is expected to be a synchronous call on the * animation thread. Operations can be added to the transaction to be applied in sync. */ void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t); /** * Called when the IME position is done animating. */ void onImeEndPositioning(int displayId, int imeTop, boolean showing, SurfaceControl.Transaction t); } }
services/core/java/com/android/server/wm/DisplayContent.java +97 −6 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; import android.view.IWindow; import android.view.InputChannel; Loading @@ -218,6 +219,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; Loading Loading @@ -570,6 +572,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ WindowState mInputMethodTarget; InsetsControlTarget mInputMethodControlTarget; /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; Loading Loading @@ -598,6 +602,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final float mWindowCornerRadius; private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>(); RemoteInsetsControlTarget mRemoteInsetsControlTarget = null; private final IBinder.DeathRecipient mRemoteInsetsDeath = () -> { synchronized (mWmService.mGlobalLock) { mRemoteInsetsControlTarget = null; } }; private RootWindowContainer mRootWindowContainer; Loading Loading @@ -1156,6 +1167,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mShellRoots.remove(windowType); } void setRemoteInsetsController(IDisplayWindowInsetsController controller) { if (mRemoteInsetsControlTarget != null) { mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath( mRemoteInsetsDeath, 0); mRemoteInsetsControlTarget = null; } if (controller != null) { try { controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0); mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller); } catch (RemoteException e) { return; } } } /** Changes the display the input window token is housed on to this one. */ void reParentWindowToken(WindowToken token) { final DisplayContent prevDc = token.getDisplayContent(); Loading Loading @@ -3383,6 +3410,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } boolean isImeAttachedToApp() { return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // An activity with override bounds should be letterboxed inside its parent bounds, // so it doesn't fill the screen. && mInputMethodTarget.mActivityRecord.matchParentBounds()); } private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) { if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; Loading @@ -3391,7 +3426,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(false /* setLayoutNeeded */); mInsetsStateController.onImeTargetChanged(target); mInputMethodControlTarget = computeImeControlTarget(); mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget); updateImeParent(); } Loading @@ -3416,11 +3452,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // An activity with override bounds should be letterboxed inside its parent bounds, // so it doesn't fill the screen. && mInputMethodTarget.mActivityRecord.matchParentBounds()) { if (isImeAttachedToApp()) { return mInputMethodTarget.mActivityRecord.getSurfaceControl(); } Loading @@ -3428,6 +3460,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mWindowContainers.getSurfaceControl(); } /** * Computes which control-target the IME should be attached to. */ @VisibleForTesting InsetsControlTarget computeImeControlTarget() { if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) { return mRemoteInsetsControlTarget; } // Otherwise, we just use the ime target return mInputMethodTarget; } void setLayoutNeeded() { if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3)); mLayoutNeeded = true; Loading Loading @@ -6688,4 +6733,50 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Context getDisplayUiContext() { return mDisplayPolicy.getSystemUiContext(); } class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) { mRemoteInsetsController = controller; } void notifyInsetsChanged() { try { mRemoteInsetsController.insetsChanged( getInsetsStateController().getRawInsetsState()); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver inset state change", e); } } @Override public void notifyInsetsControlChanged() { final InsetsStateController stateController = getInsetsStateController(); try { mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(), stateController.getControlsForDispatch(this)); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver inset state change", e); } } @Override public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { try { mRemoteInsetsController.showInsets(types, fromIme); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); } } @Override public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { try { mRemoteInsetsController.hideInsets(types, fromIme); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); } } } }