diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 9bbede311d6da..df4f2383c062b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -20,7 +20,9 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCR import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; @@ -39,7 +41,9 @@ import java.util.Optional; public class ShellInitImpl { private static final String TAG = ShellInitImpl.class.getSimpleName(); + private final DisplayController mDisplayController; private final DisplayImeController mDisplayImeController; + private final DisplayInsetsController mDisplayInsetsController; private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional mBubblesOptional; @@ -55,7 +59,10 @@ public class ShellInitImpl { private final InitImpl mImpl = new InitImpl(); - public ShellInitImpl(DisplayImeController displayImeController, + public ShellInitImpl( + DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional bubblesOptional, @@ -68,7 +75,9 @@ public class ShellInitImpl { Transitions transitions, StartingWindowController startingWindow, ShellExecutor mainExecutor) { + mDisplayController = displayController; mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; mBubblesOptional = bubblesOptional; @@ -88,7 +97,9 @@ public class ShellInitImpl { } private void init() { - // Start listening for display changes + // Start listening for display and insets changes + mDisplayController.initialize(); + mDisplayInsetsController.initialize(); mDisplayImeController.startMonitorDisplays(); // Setup the shell organizer diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index 3a7b534f3c177..ffda1f92ec902 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.common; import android.os.RemoteException; +import android.util.Slog; import android.view.IDisplayWindowRotationCallback; import android.view.IDisplayWindowRotationController; import android.view.IWindowManager; @@ -27,6 +28,7 @@ import androidx.annotation.BinderThread; import com.android.wm.shell.common.annotations.ShellMainThread; import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; /** * This module deals with display rotations coming from WM. When WM starts a rotation: after it has @@ -35,14 +37,14 @@ import java.util.ArrayList; * rotation. */ public class DisplayChangeController { + private static final String TAG = DisplayChangeController.class.getSimpleName(); private final ShellExecutor mMainExecutor; private final IWindowManager mWmService; private final IDisplayWindowRotationController mControllerImpl; - private final ArrayList mRotationListener = - new ArrayList<>(); - private final ArrayList mTmpListeners = new ArrayList<>(); + private final CopyOnWriteArrayList mRotationListener = + new CopyOnWriteArrayList<>(); public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; @@ -59,34 +61,26 @@ public class DisplayChangeController { * Adds a display rotation controller. */ public void addRotationListener(OnDisplayChangingListener listener) { - synchronized (mRotationListener) { - mRotationListener.add(listener); - } + mRotationListener.add(listener); } /** * Removes a display rotation controller. */ public void removeRotationListener(OnDisplayChangingListener listener) { - synchronized (mRotationListener) { - mRotationListener.remove(listener); - } + mRotationListener.remove(listener); } private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation, IDisplayWindowRotationCallback callback) { WindowContainerTransaction t = new WindowContainerTransaction(); - synchronized (mRotationListener) { - mTmpListeners.clear(); - // Make a local copy in case the handlers add/remove themselves. - mTmpListeners.addAll(mRotationListener); - } - for (OnDisplayChangingListener c : mTmpListeners) { + for (OnDisplayChangingListener c : mRotationListener) { c.onRotateDisplay(displayId, fromRotation, toRotation, t); } try { callback.continueRotateDisplay(toRotation, t); } catch (RemoteException e) { + Slog.e(TAG, "Failed to continue rotation", e); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index ba9ba5e5883ac..9a3bdab9f4183 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -26,6 +26,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.IDisplayWindowListener; import android.view.IWindowManager; +import android.view.InsetsState; import androidx.annotation.BinderThread; @@ -52,14 +53,6 @@ public class DisplayController { private final SparseArray mDisplays = new SparseArray<>(); private final ArrayList mDisplayChangedListeners = new ArrayList<>(); - /** - * Gets a display by id from DisplayManager. - */ - public Display getDisplay(int displayId) { - final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); - return displayManager.getDisplay(displayId); - } - public DisplayController(Context context, IWindowManager wmService, ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; @@ -67,13 +60,27 @@ public class DisplayController { mWmService = wmService; mChangeController = new DisplayChangeController(mWmService, mainExecutor); mDisplayContainerListener = new DisplayWindowListenerImpl(); + } + + /** + * Initializes the window listener. + */ + public void initialize() { try { mWmService.registerDisplayWindowListener(mDisplayContainerListener); } catch (RemoteException e) { - throw new RuntimeException("Unable to register hierarchy listener"); + throw new RuntimeException("Unable to register display controller"); } } + /** + * Gets a display by id from DisplayManager. + */ + public Display getDisplay(int displayId) { + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + return displayManager.getDisplay(displayId); + } + /** * Gets the DisplayLayout associated with a display. */ @@ -90,6 +97,16 @@ public class DisplayController { return r != null ? r.mContext : null; } + /** + * Updates the insets for a given display. + */ + public void updateDisplayInsets(int displayId, InsetsState state) { + final DisplayRecord r = mDisplays.get(displayId); + if (r != null) { + r.setInsets(state); + } + } + /** * Add a display window-container listener. It will get notified whenever a display's * configuration changes or when displays are added/removed from the WM hierarchy. @@ -134,17 +151,18 @@ public class DisplayController { if (mDisplays.get(displayId) != null) { return; } - Display display = getDisplay(displayId); + final Display display = getDisplay(displayId); if (display == null) { // It's likely that the display is private to some app and thus not // accessible by system-ui. return; } - DisplayRecord record = new DisplayRecord(); - record.mDisplayId = displayId; - record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext + + final Context context = (displayId == Display.DEFAULT_DISPLAY) + ? mContext : mContext.createDisplayContext(display); - record.mDisplayLayout = new DisplayLayout(record.mContext, display); + final DisplayRecord record = new DisplayRecord(displayId); + record.setDisplayLayout(context, new DisplayLayout(context, display)); mDisplays.put(displayId, record); for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { mDisplayChangedListeners.get(i).onDisplayAdded(displayId); @@ -154,24 +172,23 @@ public class DisplayController { private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { synchronized (mDisplays) { - DisplayRecord dr = mDisplays.get(displayId); + final DisplayRecord dr = mDisplays.get(displayId); if (dr == null) { Slog.w(TAG, "Skipping Display Configuration change on non-added" + " display."); return; } - Display display = getDisplay(displayId); + final Display display = getDisplay(displayId); if (display == null) { Slog.w(TAG, "Skipping Display Configuration change on invalid" + " display. It may have been removed."); return; } - Context perDisplayContext = mContext; - if (displayId != Display.DEFAULT_DISPLAY) { - perDisplayContext = mContext.createDisplayContext(display); - } - dr.mContext = perDisplayContext.createConfigurationContext(newConfig); - dr.mDisplayLayout = new DisplayLayout(dr.mContext, display); + final Context perDisplayContext = (displayId == Display.DEFAULT_DISPLAY) + ? mContext + : mContext.createDisplayContext(display); + final Context context = perDisplayContext.createConfigurationContext(newConfig); + dr.setDisplayLayout(context, new DisplayLayout(context, display)); for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { mDisplayChangedListeners.get(i).onDisplayConfigurationChanged( displayId, newConfig); @@ -219,9 +236,25 @@ public class DisplayController { } private static class DisplayRecord { - int mDisplayId; - Context mContext; - DisplayLayout mDisplayLayout; + private int mDisplayId; + private Context mContext; + private DisplayLayout mDisplayLayout; + private InsetsState mInsetsState = new InsetsState(); + + private DisplayRecord(int displayId) { + mDisplayId = displayId; + } + + private void setDisplayLayout(Context context, DisplayLayout displayLayout) { + mContext = context; + mDisplayLayout = displayLayout; + mDisplayLayout.setInsets(mContext.getResources(), mInsetsState); + } + + private void setInsets(InsetsState state) { + mInsetsState = state; + mDisplayLayout.setInsets(mContext.getResources(), state); + } } @BinderThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index ba59e072a126f..a7052bc49699e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -69,14 +69,17 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged protected final Executor mMainExecutor; private final TransactionPool mTransactionPool; private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; private final SparseArray mImePerDisplay = new SparseArray<>(); private final ArrayList mPositionProcessors = new ArrayList<>(); public DisplayImeController(IWindowManager wmService, DisplayController displayController, + DisplayInsetsController displayInsetsController, Executor mainExecutor, TransactionPool transactionPool) { mWmService = wmService; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mMainExecutor = mainExecutor; mTransactionPool = transactionPool; } @@ -110,11 +113,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override public void onDisplayRemoved(int displayId) { - try { - mWmService.setDisplayWindowInsetsController(displayId, null); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to remove insets controller on display " + displayId); + PerDisplay pd = mImePerDisplay.get(displayId); + if (pd == null) { + return; } + pd.unregister(); mImePerDisplay.remove(displayId); } @@ -196,12 +199,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */ - public class PerDisplay { + public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); - protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl = - new DisplayWindowInsetsControllerImpl(); InsetsSourceControl mImeSourceControl = null; int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; @@ -216,14 +217,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } public void register() { - try { - mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId); - } + mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this); } - protected void insetsChanged(InsetsState insetsState) { + public void unregister() { + mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this); + } + + @Override + public void insetsChanged(InsetsState insetsState) { if (mInsetsState.equals(insetsState)) { return; } @@ -241,8 +243,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } + @Override @VisibleForTesting - protected void insetsControlChanged(InsetsState insetsState, + public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { insetsChanged(insetsState); InsetsSourceControl imeSourceControl = null; @@ -303,7 +306,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - protected void showInsets(int types, boolean fromIme) { + @Override + public void showInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } @@ -311,8 +315,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged startAnimation(true /* show */, false /* forceRestart */); } - - protected void hideInsets(int types, boolean fromIme) { + @Override + public void hideInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } @@ -320,6 +324,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged startAnimation(false /* show */, false /* forceRestart */); } + @Override public void topFocusedWindowChanged(String packageName) { // Do nothing } @@ -493,47 +498,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged dispatchVisibilityChanged(mDisplayId, isShowing); } } - - @VisibleForTesting - @BinderThread - public class DisplayWindowInsetsControllerImpl - extends IDisplayWindowInsetsController.Stub { - @Override - public void topFocusedWindowChanged(String packageName) throws RemoteException { - mMainExecutor.execute(() -> { - PerDisplay.this.topFocusedWindowChanged(packageName); - }); - } - - @Override - public void insetsChanged(InsetsState insetsState) throws RemoteException { - mMainExecutor.execute(() -> { - PerDisplay.this.insetsChanged(insetsState); - }); - } - - @Override - public void insetsControlChanged(InsetsState insetsState, - InsetsSourceControl[] activeControls) throws RemoteException { - mMainExecutor.execute(() -> { - PerDisplay.this.insetsControlChanged(insetsState, activeControls); - }); - } - - @Override - public void showInsets(int types, boolean fromIme) throws RemoteException { - mMainExecutor.execute(() -> { - PerDisplay.this.showInsets(types, fromIme); - }); - } - - @Override - public void hideInsets(int types, boolean fromIme) throws RemoteException { - mMainExecutor.execute(() -> { - PerDisplay.this.hideInsets(types, fromIme); - }); - } - } } void removeImeSurface() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java new file mode 100644 index 0000000000000..5f3de7ec35c0a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2021 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.wm.shell.common; + +import android.os.RemoteException; +import android.util.Slog; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; +import android.view.InsetsSourceControl; +import android.view.InsetsState; + +import androidx.annotation.BinderThread; + +import com.android.wm.shell.common.annotations.ShellMainThread; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Manages insets from the core. + */ +public class DisplayInsetsController implements DisplayController.OnDisplaysChangedListener { + private static final String TAG = "DisplayInsetsController"; + + private final IWindowManager mWmService; + private final ShellExecutor mMainExecutor; + private final DisplayController mDisplayController; + private final SparseArray mInsetsPerDisplay = new SparseArray<>(); + private final SparseArray> mListeners = + new SparseArray<>(); + + public DisplayInsetsController(IWindowManager wmService, DisplayController displayController, + ShellExecutor mainExecutor) { + mWmService = wmService; + mDisplayController = displayController; + mMainExecutor = mainExecutor; + } + + /** + * Starts listening for insets for each display. + **/ + public void initialize() { + mDisplayController.addDisplayWindowListener(this); + } + + /** + * Adds a callback to listen for insets changes for a particular display. Note that the + * listener will not be updated with the existing state of the insets on that display. + */ + public void addInsetsChangedListener(int displayId, OnInsetsChangedListener listener) { + CopyOnWriteArrayList listeners = mListeners.get(displayId); + if (listeners == null) { + listeners = new CopyOnWriteArrayList<>(); + mListeners.put(displayId, listeners); + } + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + /** + * Removes a callback listening for insets changes from a particular display. + */ + public void removeInsetsChangedListener(int displayId, OnInsetsChangedListener listener) { + CopyOnWriteArrayList listeners = mListeners.get(displayId); + if (listeners == null) { + return; + } + listeners.remove(listener); + } + + @Override + public void onDisplayAdded(int displayId) { + PerDisplay pd = new PerDisplay(displayId); + pd.register(); + mInsetsPerDisplay.put(displayId, pd); + } + + @Override + public void onDisplayRemoved(int displayId) { + PerDisplay pd = mInsetsPerDisplay.get(displayId); + if (pd == null) { + return; + } + pd.unregister(); + mInsetsPerDisplay.remove(displayId); + } + + /** + * An implementation of {@link IDisplayWindowInsetsController} for a given display id. + **/ + public class PerDisplay { + private final int mDisplayId; + private final DisplayWindowInsetsControllerImpl mInsetsControllerImpl = + new DisplayWindowInsetsControllerImpl(); + + public PerDisplay(int displayId) { + mDisplayId = displayId; + } + + public void register() { + try { + mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId); + } + } + + public void unregister() { + try { + mWmService.setDisplayWindowInsetsController(mDisplayId, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to remove insets controller on display " + mDisplayId); + } + } + + private void insetsChanged(InsetsState insetsState) { + CopyOnWriteArrayList listeners = mListeners.get(mDisplayId); + if (listeners == null) { + return; + } + mDisplayController.updateDisplayInsets(mDisplayId, insetsState); + for (OnInsetsChangedListener listener : listeners) { + listener.insetsChanged(insetsState); + } + } + + private void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + CopyOnWriteArrayList listeners = mListeners.get(mDisplayId); + if (listeners == null) { + return; + } + for (OnInsetsChangedListener listener : listeners) { + listener.insetsControlChanged(insetsState, activeControls); + } + } + + private void showInsets(int types, boolean fromIme) { + CopyOnWriteArrayList listeners = mListeners.get(mDisplayId); + if (listeners == null) { + return; + } + for (OnInsetsChangedListener listener : listeners) { + listener.showInsets(types, fromIme); + } + } + + private void hideInsets(int types, boolean fromIme) { + CopyOnWriteArrayList listeners = mListeners.get(mDisplayId); + if (listeners == null) { + return; + } + for (OnInsetsChangedListener listener : listeners) { + listener.hideInsets(types, fromIme); + } + } + + private void topFocusedWindowChanged(String packageName) { + CopyOnWriteArrayList listeners = mListeners.get(mDisplayId); + if (listeners == null) { + return; + } + for (OnInsetsChangedListener listener : listeners) { + listener.topFocusedWindowChanged(packageName); + } + } + + @BinderThread + private class DisplayWindowInsetsControllerImpl + extends IDisplayWindowInsetsController.Stub { + @Override + public void topFocusedWindowChanged(String packageName) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.topFocusedWindowChanged(packageName); + }); + } + + @Override + public void insetsChanged(InsetsState insetsState) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.insetsChanged(insetsState); + }); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.insetsControlChanged(insetsState, activeControls); + }); + } + + @Override + public void showInsets(int types, boolean fromIme) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.showInsets(types, fromIme); + }); + } + + @Override + public void hideInsets(int types, boolean fromIme) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.hideInsets(types, fromIme); + }); + } + } + } + + /** + * Gets notified whenever the insets change. + * + * @see IDisplayWindowInsetsController + */ + @ShellMainThread + public interface OnInsetsChangedListener { + /** + * Called when top focused window changes to determine whether or not to take over insets + * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false. + * @param packageName: Passes the top package name + */ + void topFocusedWindowChanged(String packageName); + + /** + * Called when the window insets configuration has changed. + */ + void insetsChanged(InsetsState insetsState); + + /** + * Called when this window retrieved control over a specified set of insets sources. + */ + void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls); + + /** + * Called when a set of insets source window should be shown by policy. + * + * @param types internal insets types (WindowInsets.Type.InsetsType) to show + * @param fromIme true if this request originated from IME (InputMethodService). + */ + void showInsets(int types, boolean fromIme); + + /** + * Called when a set of insets source window should be hidden by policy. + * + * @param types internal insets types (WindowInsets.Type.InsetsType) to hide + * @param fromIme true if this request originated from IME (InputMethodService). + */ + void hideInsets(int types, boolean fromIme); + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index b90283feb1840..962aca122b4d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -25,6 +25,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON import static android.util.RotationUtils.rotateBounds; import static android.util.RotationUtils.rotateInsets; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -44,7 +45,10 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.Surface; +import android.view.WindowInsets; import com.android.internal.R; @@ -85,6 +89,7 @@ public class DisplayLayout { private boolean mAllowSeamlessRotationDespiteNavBarMoving = false; private boolean mNavigationBarCanMove = false; private boolean mReverseDefaultRotation = false; + private InsetsState mInsetsState = new InsetsState(); @Override public boolean equals(Object o) { @@ -105,7 +110,8 @@ public class DisplayLayout { == other.mAllowSeamlessRotationDespiteNavBarMoving && mNavigationBarCanMove == other.mNavigationBarCanMove && mReverseDefaultRotation == other.mReverseDefaultRotation - && mNavBarFrameHeight == other.mNavBarFrameHeight; + && mNavBarFrameHeight == other.mNavBarFrameHeight + && Objects.equals(mInsetsState, other.mInsetsState); } @Override @@ -113,7 +119,7 @@ public class DisplayLayout { return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi, mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar, mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving, - mNavigationBarCanMove, mReverseDefaultRotation); + mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState); } /** @@ -164,6 +170,7 @@ public class DisplayLayout { mNavBarFrameHeight = dl.mNavBarFrameHeight; mNonDecorInsets.set(dl.mNonDecorInsets); mStableInsets.set(dl.mStableInsets); + mInsetsState.set(dl.mInsetsState, true /* copySources */); } private void init(DisplayInfo info, Resources res, boolean hasNavigationBar, @@ -183,9 +190,17 @@ public class DisplayLayout { recalcInsets(res); } + /** + * Updates the current insets. + */ + public void setInsets(Resources res, InsetsState state) { + mInsetsState = state; + recalcInsets(res); + } + private void recalcInsets(Resources res) { - computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets, - mHasNavigationBar); + computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mInsetsState, mUiMode, + mNonDecorInsets, mHasNavigationBar); mStableInsets.set(mNonDecorInsets); if (mHasStatusBar) { convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar); @@ -259,7 +274,7 @@ public class DisplayLayout { return mWidth > mHeight; } - /** Get the navbar frame height (used by ime). */ + /** Get the navbar frame (or window) height (used by ime). */ public int navBarFrameHeight() { return mNavBarFrameHeight; } @@ -328,21 +343,29 @@ public class DisplayLayout { * @param outInsets the insets to return */ static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth, - int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, - boolean hasNavigationBar) { + int displayHeight, DisplayCutout displayCutout, InsetsState insetsState, int uiMode, + Rect outInsets, boolean hasNavigationBar) { outInsets.setEmpty(); // Only navigation bar if (hasNavigationBar) { + final InsetsSource extraNavBar = insetsState.getSource(ITYPE_EXTRA_NAVIGATION_BAR); + final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible(); int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation); int navBarSize = getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode); if (position == NAV_BAR_BOTTOM) { - outInsets.bottom = navBarSize; + outInsets.bottom = hasExtraNav + ? Math.max(navBarSize, extraNavBar.getFrame().height()) + : navBarSize; } else if (position == NAV_BAR_RIGHT) { - outInsets.right = navBarSize; + outInsets.right = hasExtraNav + ? Math.max(navBarSize, extraNavBar.getFrame().width()) + : navBarSize; } else if (position == NAV_BAR_LEFT) { - outInsets.left = navBarSize; + outInsets.left = hasExtraNav + ? Math.max(navBarSize, extraNavBar.getFrame().width()) + : navBarSize; } } @@ -364,13 +387,13 @@ public class DisplayLayout { * @param outInsets the insets to return */ static void computeStableInsets(Resources res, int displayRotation, int displayWidth, - int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, - boolean hasNavigationBar, boolean hasStatusBar) { + int displayHeight, DisplayCutout displayCutout, InsetsState insetsState, int uiMode, + Rect outInsets, boolean hasNavigationBar, boolean hasStatusBar) { outInsets.setEmpty(); // Navigation bar and status bar. computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout, - uiMode, outInsets, hasNavigationBar); + insetsState, uiMode, outInsets, hasNavigationBar); convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight, hasStatusBar); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index ef046d48e1cfe..b888450442637 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -58,7 +58,7 @@ public class DisplayImeControllerTest { mT = mock(SurfaceControl.Transaction.class); mMock = mock(IInputMethodManager.class); mExecutor = spy(Runnable::run); - mPerDisplay = new DisplayImeController(null, null, mExecutor, new TransactionPool() { + mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() { @Override public SurfaceControl.Transaction acquire() { return mT; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java new file mode 100644 index 0000000000000..b66c2b4aee9ba --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021 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.wm.shell.common; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.RemoteException; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; +import android.view.InsetsSourceControl; +import android.view.InsetsState; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TestShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@SmallTest +public class DisplayInsetsControllerTest { + + private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 10; + + @Mock + private IWindowManager mWm; + @Mock + private DisplayController mDisplayController; + private DisplayInsetsController mController; + private SparseArray mInsetsControllersByDisplayId; + private TestShellExecutor mExecutor; + + private ArgumentCaptor mDisplayIdCaptor; + private ArgumentCaptor mInsetsControllerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mExecutor = new TestShellExecutor(); + mInsetsControllersByDisplayId = new SparseArray<>(); + mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class); + mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class); + mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor); + addDisplay(DEFAULT_DISPLAY); + } + + @Test + public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService() + throws RemoteException { + addDisplay(SECOND_DISPLAY); + + verify(mWm).setDisplayWindowInsetsController(eq(SECOND_DISPLAY), notNull()); + } + + @Test + public void testOnDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService() + throws RemoteException { + addDisplay(SECOND_DISPLAY); + removeDisplay(SECOND_DISPLAY); + + verify(mWm).setDisplayWindowInsetsController(SECOND_DISPLAY, null); + } + + @Test + public void testPerDisplayListenerCallback() throws RemoteException { + TrackedListener defaultListener = new TrackedListener(); + TrackedListener secondListener = new TrackedListener(); + addDisplay(SECOND_DISPLAY); + mController.addInsetsChangedListener(DEFAULT_DISPLAY, defaultListener); + mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener); + + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false); + mExecutor.flushAll(); + + assertTrue(defaultListener.topFocusedWindowChangedCount == 1); + assertTrue(defaultListener.insetsChangedCount == 1); + assertTrue(defaultListener.insetsControlChangedCount == 1); + assertTrue(defaultListener.showInsetsCount == 1); + assertTrue(defaultListener.hideInsetsCount == 1); + + assertTrue(secondListener.topFocusedWindowChangedCount == 0); + assertTrue(secondListener.insetsChangedCount == 0); + assertTrue(secondListener.insetsControlChangedCount == 0); + assertTrue(secondListener.showInsetsCount == 0); + assertTrue(secondListener.hideInsetsCount == 0); + + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false); + mExecutor.flushAll(); + + assertTrue(defaultListener.topFocusedWindowChangedCount == 1); + assertTrue(defaultListener.insetsChangedCount == 1); + assertTrue(defaultListener.insetsControlChangedCount == 1); + assertTrue(defaultListener.showInsetsCount == 1); + assertTrue(defaultListener.hideInsetsCount == 1); + + assertTrue(secondListener.topFocusedWindowChangedCount == 1); + assertTrue(secondListener.insetsChangedCount == 1); + assertTrue(secondListener.insetsControlChangedCount == 1); + assertTrue(secondListener.showInsetsCount == 1); + assertTrue(secondListener.hideInsetsCount == 1); + } + + private void addDisplay(int displayId) throws RemoteException { + mController.onDisplayAdded(displayId); + verify(mWm, times(mInsetsControllersByDisplayId.size() + 1)) + .setDisplayWindowInsetsController(mDisplayIdCaptor.capture(), + mInsetsControllerCaptor.capture()); + List displayIds = mDisplayIdCaptor.getAllValues(); + List insetsControllers = + mInsetsControllerCaptor.getAllValues(); + for (int i = 0; i < displayIds.size(); i++) { + mInsetsControllersByDisplayId.put(displayIds.get(i), insetsControllers.get(i)); + } + } + + private void removeDisplay(int displayId) { + mController.onDisplayRemoved(displayId); + mInsetsControllersByDisplayId.remove(displayId); + } + + private static class TrackedListener implements + DisplayInsetsController.OnInsetsChangedListener { + int topFocusedWindowChangedCount = 0; + int insetsChangedCount = 0; + int insetsControlChangedCount = 0; + int showInsetsCount = 0; + int hideInsetsCount = 0; + + @Override + public void topFocusedWindowChanged(String packageName) { + topFocusedWindowChangedCount++; + } + + @Override + public void insetsChanged(InsetsState insetsState) { + insetsChangedCount++; + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsControlChangedCount++; + } + + @Override + public void showInsets(int types, boolean fromIme) { + showInsetsCount++; + } + + @Override + public void hideInsets(int types, boolean fromIme) { + hideInsetsCount++; + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 84565e529e9ec..376245609c192 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -685,6 +685,7 @@ public class ShellTransitionTests { // No remote stuff happening, so this can't be hit } DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor); + out.initialize(); try { displayListener[0].onDisplayAdded(DEFAULT_DISPLAY); mMainExecutor.flushAll(); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java index 39a8bd94bf376..dbdc460b7d83c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -25,6 +25,7 @@ import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -58,10 +59,10 @@ public class TvWMShellModule { @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, - TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainExecutor, - transactionPool); + DisplayController displayController, DisplayInsetsController displayInsetsController, + @ShellMainThread ShellExecutor mainExecutor, TransactionPool transactionPool) { + return new DisplayImeController(wmService, displayController, displayInsetsController, + mainExecutor, transactionPool); } // diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 7e3553a63958e..7937dfd0dbdc4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -44,6 +44,7 @@ import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -108,6 +109,14 @@ public abstract class WMShellBaseModule { return new DisplayController(context, wmService, mainExecutor); } + @WMSingleton + @Provides + static DisplayInsetsController provideDisplayInsetsController( IWindowManager wmService, + DisplayController displayController, + @ShellMainThread ShellExecutor mainExecutor) { + return new DisplayInsetsController(wmService, displayController, mainExecutor); + } + @WMSingleton @Provides static DisplayLayout provideDisplayLayout() { @@ -452,7 +461,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController, + static ShellInitImpl provideShellInitImpl(DisplayController displayController, + DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional bubblesOptional, @@ -465,7 +476,9 @@ public abstract class WMShellBaseModule { Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellInitImpl(displayImeController, + return new ShellInitImpl(displayController, + displayImeController, + displayInsetsController, dragAndDropController, shellTaskOrganizer, bubblesOptional, diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 6397ce680a828..83c2a9b1be339 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -28,6 +28,7 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -83,10 +84,11 @@ public class WMShellModule { @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, + DisplayController displayController, DisplayInsetsController displayInsetsController, + @ShellMainThread ShellExecutor mainExecutor, TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainExecutor, - transactionPool); + return new DisplayImeController(wmService, displayController, displayInsetsController, + mainExecutor, transactionPool); } //