From f96c90ac6c4e12113b5d0187bf3be9b39e7027f4 Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Wed, 26 Sep 2018 16:55:15 +0200 Subject: [PATCH] A brave new world for window insets (1/n) This CL starts a journey to discover a brave new inset world. The path to get us there may be rocky, but it's going to be rocky. One of the main pledges of the new API is that an app can retrieve what is causing a certain inset easily. For that, we need to dispatch metadata who is causing what inset, such that we can query it from the client side. Furthermore, the client will be able to manipulate insets directly, but also listen to animation changes. We don't want to go through window manager for that, thus, there needs to be a local codepath from (global window state -> WindowInsets). Because we have these two requirements, we dispatch the relevant global window state for insets, represented by InsetsState, and dispatch it to the client. On the client side we take the frame and the InsetsState and generate WindowInsets out of it. Bug: 118118435 Test: InsetsSourceTest, InsetsStateTest, InsetsSourceProviderTest, InsetsStateControllerTest Change-Id: I2bfe9dda376512916261823fc2ee35cbedeb6731 --- api/current.txt | 1 + config/hiddenapi-light-greylist.txt | 1 - .../service/wallpaper/WallpaperService.java | 9 +- core/java/android/view/IWindow.aidl | 7 + core/java/android/view/IWindowSession.aidl | 10 +- core/java/android/view/InsetsController.java | 55 ++++ core/java/android/view/InsetsSource.java | 159 ++++++++++++ core/java/android/view/InsetsState.aidl | 19 ++ core/java/android/view/InsetsState.java | 240 ++++++++++++++++++ core/java/android/view/ViewRootImpl.java | 101 ++++++-- .../com/android/internal/os/SomeArgs.java | 2 + .../android/internal/view/BaseIWindow.java | 5 + .../src/android/view/InsetsSourceTest.java | 103 ++++++++ .../src/android/view/InsetsStateTest.java | 130 ++++++++++ graphics/java/android/graphics/Insets.java | 11 + graphics/java/android/graphics/Rect.java | 26 ++ .../com/android/server/wm/DisplayContent.java | 29 +++ .../com/android/server/wm/DisplayPolicy.java | 16 ++ .../server/wm/InsetsSourceProvider.java | 87 +++++++ .../server/wm/InsetsStateController.java | 104 ++++++++ .../java/com/android/server/wm/Session.java | 17 +- .../server/wm/TaskSnapshotSurface.java | 6 +- .../server/wm/WindowManagerService.java | 8 +- .../com/android/server/wm/WindowState.java | 24 ++ .../server/wm/InsetsSourceProviderTest.java | 78 ++++++ .../server/wm/InsetsStateControllerTest.java | 79 ++++++ .../com/android/server/wm/TestIWindow.java | 4 + .../windowmanagerstresstest/MainActivity.java | 8 +- 28 files changed, 1304 insertions(+), 35 deletions(-) create mode 100644 core/java/android/view/InsetsController.java create mode 100644 core/java/android/view/InsetsSource.java create mode 100644 core/java/android/view/InsetsState.aidl create mode 100644 core/java/android/view/InsetsState.java create mode 100644 core/tests/coretests/src/android/view/InsetsSourceTest.java create mode 100644 core/tests/coretests/src/android/view/InsetsStateTest.java create mode 100644 services/core/java/com/android/server/wm/InsetsSourceProvider.java create mode 100644 services/core/java/com/android/server/wm/InsetsStateController.java create mode 100644 services/tests/servicestests/src/com/android/server/wm/InsetsSourceProviderTest.java create mode 100644 services/tests/servicestests/src/com/android/server/wm/InsetsStateControllerTest.java diff --git a/api/current.txt b/api/current.txt index 115c1c1e1701a..2ba2f7a6cd080 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13940,6 +13940,7 @@ package android.graphics { } public final class Insets { + method public static android.graphics.Insets add(android.graphics.Insets, android.graphics.Insets); method public static android.graphics.Insets of(int, int, int, int); method public static android.graphics.Insets of(android.graphics.Rect); field public static final android.graphics.Insets NONE; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index f78506bd4625a..912953e04bbc3 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -1461,7 +1461,6 @@ Landroid/view/IWindowManager;->setShelfHeight(ZI)V Landroid/view/IWindowManager;->setStrictModeVisualIndicatorPreference(Ljava/lang/String;)V Landroid/view/IWindowManager;->showStrictModeViolation(Z)V Landroid/view/IWindowManager;->thawRotation()V -Landroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIIJLandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/view/DisplayCutout$ParcelableWrapper;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I Landroid/view/IWindowSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/IWindowSession; Landroid/view/IWindowSession;->finishDrawing(Landroid/view/IWindow;)V Landroid/view/IWindowSession;->getInTouchMode()Z diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f6bb762a0bef1..45d53f340f35a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -56,6 +56,7 @@ import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; +import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -184,6 +185,7 @@ public abstract class WallpaperService extends Service { final DisplayCutout.ParcelableWrapper mDisplayCutout = new DisplayCutout.ParcelableWrapper(); DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; + final InsetsState mInsetsState = new InsetsState(); final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); final WindowManager.LayoutParams mLayout @@ -803,9 +805,11 @@ public abstract class WallpaperService extends Service { mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; mInputChannel = new InputChannel(); + if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets, - mOutsets, mDisplayCutout, mInputChannel) < 0) { + mOutsets, mDisplayCutout, mInputChannel, + mInsetsState) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -831,7 +835,8 @@ public abstract class WallpaperService extends Service { mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets, mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame, - mDisplayCutout, mMergedConfiguration, mSurfaceHolder.mSurface); + mDisplayCutout, mMergedConfiguration, mSurfaceHolder.mSurface, + mInsetsState); if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface + ", frame=" + mWinFrame); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 4b8b7f304b0f3..af41b6942a5ee 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -24,6 +24,7 @@ import android.view.DragEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.DisplayCutout; +import android.view.InsetsState; import com.android.internal.os.IResultReceiver; import android.util.MergedConfiguration; @@ -53,6 +54,12 @@ oneway interface IWindow { in MergedConfiguration newMergedConfiguration, in Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, in DisplayCutout.ParcelableWrapper displayCutout); + + /** + * Called when the window insets configuration has changed. + */ + void insetsChanged(in InsetsState insetsState); + void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index bedfa9ff133ce..97625869209d4 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -28,6 +28,7 @@ import android.view.IWindow; import android.view.IWindowId; import android.view.MotionEvent; import android.view.WindowManager; +import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; @@ -40,10 +41,11 @@ interface IWindowSession { int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out Rect outOutsets, - out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel); + out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, + out InsetsState insetsState); int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, - out Rect outStableInsets); + out Rect outStableInsets, out InsetsState insetsState); void remove(IWindow window); /** @@ -86,6 +88,7 @@ interface IWindowSession { * config for window, if it is now becoming visible and the merged configuration has changed * since it was last displayed. * @param outSurface Object in which is placed the new display surface. + * @param insetsState The current insets state in the system. * * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. @@ -96,7 +99,8 @@ interface IWindowSession { out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets, out Rect outOutsets, out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, - out MergedConfiguration outMergedConfiguration, out Surface outSurface); + out MergedConfiguration outMergedConfiguration, out Surface outSurface, + out InsetsState insetsState); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java new file mode 100644 index 0000000000000..7841d0417a2ba --- /dev/null +++ b/core/java/android/view/InsetsController.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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.graphics.Rect; + +import java.io.PrintWriter; + +/** + * Implements {@link WindowInsetsController} on the client. + */ +class InsetsController { + + private final InsetsState mState = new InsetsState(); + private final Rect mFrame = new Rect(); + + void onFrameChanged(Rect frame) { + mFrame.set(frame); + } + + public InsetsState getState() { + return mState; + } + + public void setState(InsetsState state) { + mState.set(state); + } + + /** + * @see InsetsState#calculateInsets + */ + WindowInsets calculateInsets(boolean isScreenRound, + boolean alwaysConsumeNavBar, DisplayCutout cutout) { + return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout); + } + + void dump(String prefix, PrintWriter pw) { + pw.println(prefix); pw.println("InsetsController:"); + mState.dump(prefix + " ", pw); + } +} diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java new file mode 100644 index 0000000000000..0cb8ad72f1029 --- /dev/null +++ b/core/java/android/view/InsetsSource.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 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.graphics.Insets; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.InsetsState.InternalInsetType; + +import java.io.PrintWriter; + +/** + * Represents the state of a single window generating insets for clients. + * @hide + */ +public class InsetsSource implements Parcelable { + + private final @InternalInsetType int mType; + + /** Frame of the source in screen coordinate space */ + private final Rect mFrame; + private boolean mVisible; + + private final Rect mTmpFrame = new Rect(); + + public InsetsSource(@InternalInsetType int type) { + mType = type; + mFrame = new Rect(); + } + + public InsetsSource(InsetsSource other) { + mType = other.mType; + mFrame = new Rect(other.mFrame); + mVisible = other.mVisible; + } + + public void setFrame(Rect frame) { + mFrame.set(frame); + } + + public void setVisible(boolean visible) { + mVisible = visible; + } + + public @InternalInsetType int getType() { + return mType; + } + + public Rect getFrame() { + return mFrame; + } + + /** + * Calculates the insets this source will cause to a client window. + * + * @param relativeFrame The frame to calculate the insets relative to. + * @param ignoreVisibility If true, always reports back insets even if source isn't visible. + * @return The resulting insets. + */ + public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { + if (!ignoreVisibility && !mVisible) { + return Insets.NONE; + } + if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) { + return Insets.NONE; + } + + // Intersecting at top/bottom + if (mTmpFrame.width() == relativeFrame.width()) { + if (mTmpFrame.top == relativeFrame.top) { + return Insets.of(0, mTmpFrame.height(), 0, 0); + } else { + return Insets.of(0, 0, 0, mTmpFrame.height()); + } + } + // Intersecting at left/right + else if (mTmpFrame.height() == relativeFrame.height()) { + if (mTmpFrame.left == relativeFrame.left) { + return Insets.of(mTmpFrame.width(), 0, 0, 0); + } else { + return Insets.of(0, 0, mTmpFrame.width(), 0); + } + } else { + return Insets.NONE; + } + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); + pw.print(" frame="); pw.print(mFrame.toShortString()); + pw.print(" visible="); pw.print(mVisible); + pw.println(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InsetsSource that = (InsetsSource) o; + + if (mType != that.mType) return false; + if (mVisible != that.mVisible) return false; + return mFrame.equals(that.mFrame); + } + + @Override + public int hashCode() { + int result = mType; + result = 31 * result + mFrame.hashCode(); + result = 31 * result + (mVisible ? 1 : 0); + return result; + } + + public InsetsSource(Parcel in) { + mType = in.readInt(); + mFrame = in.readParcelable(null /* loader */); + mVisible = in.readBoolean(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeParcelable(mFrame, 0 /* flags*/); + dest.writeBoolean(mVisible); + } + + public static final Creator CREATOR = new Creator() { + + public InsetsSource createFromParcel(Parcel in) { + return new InsetsSource(in); + } + + public InsetsSource[] newArray(int size) { + return new InsetsSource[size]; + } + }; +} diff --git a/core/java/android/view/InsetsState.aidl b/core/java/android/view/InsetsState.aidl new file mode 100644 index 0000000000000..d02ddd15a8c90 --- /dev/null +++ b/core/java/android/view/InsetsState.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017, 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; + +parcelable InsetsState; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java new file mode 100644 index 0000000000000..9895adcad23a2 --- /dev/null +++ b/core/java/android/view/InsetsState.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2018 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.annotation.IntDef; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Holder for state of system windows that cause window insets for all other windows in the system. + * @hide + */ +public class InsetsState implements Parcelable { + + /** + * Internal representation of inset source types. This is different from the public API in + * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows + * at the same time. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "TYPE", value = { + TYPE_TOP_BAR, + TYPE_SIDE_BAR_1, + TYPE_SIDE_BAR_2, + TYPE_SIDE_BAR_3, + TYPE_IME + }) + public @interface InternalInsetType {} + + static final int FIRST_TYPE = 0; + + /** Top bar. Can be status bar or caption in freeform windowing mode. */ + public static final int TYPE_TOP_BAR = FIRST_TYPE; + + /** + * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar + * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have + * multiple, like Android Auto. + */ + public static final int TYPE_SIDE_BAR_1 = 1; + public static final int TYPE_SIDE_BAR_2 = 2; + public static final int TYPE_SIDE_BAR_3 = 3; + + /** Input method window. */ + public static final int TYPE_IME = 4; + static final int LAST_TYPE = TYPE_IME; + + // Derived types + + /** First side bar is navigation bar. */ + public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1; + + /** A shelf is the same as the navigation bar. */ + public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR; + + private final ArrayMap mSources = new ArrayMap<>(); + + public InsetsState() { + } + + /** + * Calculates {@link WindowInsets} based on the current source configuration. + * + * @param frame The frame to calculate the insets relative to. + * @return The calculated insets. + */ + public WindowInsets calculateInsets(Rect frame, boolean isScreenRound, + boolean alwaysConsumeNavBar, DisplayCutout cutout) { + Insets systemInsets = Insets.NONE; + Insets maxInsets = Insets.NONE; + final Rect relativeFrame = new Rect(frame); + final Rect relativeFrameMax = new Rect(frame); + for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { + InsetsSource source = mSources.get(type); + if (source == null) { + continue; + } + systemInsets = processSource(source, systemInsets, relativeFrame, + false /* ignoreVisibility */); + + // IME won't be reported in max insets as the size depends on the EditorInfo of the IME + // target. + if (source.getType() != TYPE_IME) { + maxInsets = processSource(source, maxInsets, relativeFrameMax, + true /* ignoreVisibility */); + } + } + return new WindowInsets(new Rect(systemInsets), null, new Rect(maxInsets), isScreenRound, + alwaysConsumeNavBar, cutout); + } + + private Insets processSource(InsetsSource source, Insets insets, Rect relativeFrame, + boolean ignoreVisibility) { + Insets currentInsets = source.calculateInsets(relativeFrame, ignoreVisibility); + insets = Insets.add(currentInsets, insets); + relativeFrame.inset(insets); + return insets; + } + + public InsetsSource getSource(@InternalInsetType int type) { + return mSources.computeIfAbsent(type, InsetsSource::new); + } + + /** + * Modifies the state of this class to exclude a certain type to make it ready for dispatching + * to the client. + * + * @param type The {@link InternalInsetType} of the source to remove + */ + public void removeSource(int type) { + mSources.remove(type); + } + + public void set(InsetsState other) { + set(other, false /* copySources */); + } + + public void set(InsetsState other, boolean copySources) { + mSources.clear(); + if (copySources) { + for (int i = 0; i < other.mSources.size(); i++) { + InsetsSource source = other.mSources.valueAt(i); + mSources.put(source.getType(), new InsetsSource(source)); + } + } else { + mSources.putAll(other.mSources); + } + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "InsetsState"); + for (int i = mSources.size() - 1; i >= 0; i--) { + mSources.valueAt(i).dump(prefix + " ", pw); + } + } + + static String typeToString(int type) { + switch (type) { + case TYPE_TOP_BAR: + return "TYPE_TOP_BAR"; + case TYPE_SIDE_BAR_1: + return "TYPE_SIDE_BAR_1"; + case TYPE_SIDE_BAR_2: + return "TYPE_SIDE_BAR_2"; + case TYPE_SIDE_BAR_3: + return "TYPE_SIDE_BAR_3"; + case TYPE_IME: + return "TYPE_IME"; + default: + return "TYPE_UNKNOWN"; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + + InsetsState state = (InsetsState) o; + + if (mSources.size() != state.mSources.size()) { + return false; + } + for (int i = mSources.size() - 1; i >= 0; i--) { + InsetsSource source = mSources.valueAt(i); + InsetsSource otherSource = state.mSources.get(source.getType()); + if (otherSource == null) { + return false; + } + if (!otherSource.equals(source)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return mSources.hashCode(); + } + + public InsetsState(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSources.size()); + for (int i = 0; i < mSources.size(); i++) { + dest.writeParcelable(mSources.valueAt(i), 0 /* flags */); + } + } + + public static final Creator CREATOR = new Creator() { + + public InsetsState createFromParcel(Parcel in) { + return new InsetsState(in); + } + + public InsetsState[] newArray(int size) { + return new InsetsState[size]; + } + }; + + public void readFromParcel(Parcel in) { + mSources.clear(); + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final InsetsSource source = in.readParcelable(null /* loader */); + mSources.put(source.getType(), source); + } + } +} + diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 484c6f38e9623..872f147c370f6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -160,6 +160,19 @@ public final class ViewRootImpl implements ViewParent, */ private static final boolean MT_RENDERER_AVAILABLE = true; + /** + * If set to true, the view system will switch from using rectangles retrieved from window to + * dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets + * directly from the full configuration, enabling richer information about the insets state, as + * well as new APIs to control it frame-by-frame, and synchronize animations with it. + *

+ * Only switch this to true once the new insets system is productionized and the old APIs are + * fully migrated over. + */ + private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets"; + private static final boolean USE_NEW_INSETS = + SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false); + /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. @@ -432,6 +445,8 @@ public final class ViewRootImpl implements ViewParent, boolean mAdded; boolean mAddedTouchMode; + final Rect mTmpFrame = new Rect(); + // These are accessed by multiple threads. final Rect mWinFrame; // frame given by window manager. @@ -444,6 +459,7 @@ public final class ViewRootImpl implements ViewParent, final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeNavBar; + private InsetsState mPendingInsets; final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); @@ -531,6 +547,8 @@ public final class ViewRootImpl implements ViewParent, InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; + private final InsetsController mInsetsController = new InsetsController(); + static final class SystemUiVisibilityInfo { int seq; int globalVisibility; @@ -797,9 +815,11 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, - getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, + getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, - mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); + mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, + mInsetsController.getState()); + setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; mView = null; @@ -826,6 +846,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mAlwaysConsumeNavBar = (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0; mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar; + mPendingInsets = mInsetsController.getState(); if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -1780,7 +1801,8 @@ public final class ViewRootImpl implements ViewParent, Rect stableInsets = mDispatchStableInsets; DisplayCutout displayCutout = mDispatchDisplayCutout; // For dispatch we preserve old logic, but for direct requests from Views we allow to - // immediately use pending insets. + // immediately use pending insets. This is such that getRootWindowInsets returns the + // result from the layout hint before we ran a traversal shortly after adding a window. if (!forceConstruct && (!mPendingContentInsets.equals(contentInsets) || !mPendingStableInsets.equals(stableInsets) || @@ -1797,10 +1819,16 @@ public final class ViewRootImpl implements ViewParent, } contentInsets = ensureInsetsNonNegative(contentInsets, "content"); stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); - mLastWindowInsets = new WindowInsets(contentInsets, - null /* windowDecorInsets */, stableInsets, - mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeNavBar, displayCutout); + if (USE_NEW_INSETS) { + mLastWindowInsets = mInsetsController.calculateInsets( + mContext.getResources().getConfiguration().isScreenRound(), + mAttachInfo.mAlwaysConsumeNavBar, displayCutout); + } else { + mLastWindowInsets = new WindowInsets(contentInsets, + null /* windowDecorInsets */, stableInsets, + mContext.getResources().getConfiguration().isScreenRound(), + mAttachInfo.mAlwaysConsumeNavBar, displayCutout); + } } return mLastWindowInsets; } @@ -2000,6 +2028,9 @@ public final class ViewRootImpl implements ViewParent, if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) { insetsChanged = true; } + if (!mPendingInsets.equals(mInsetsController.getState())) { + insetsChanged = true; + } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; @@ -2193,6 +2224,8 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mStableInsets); final boolean cutoutChanged = !mPendingDisplayCutout.equals( mAttachInfo.mDisplayCutout); + final boolean insetsStateChanged = !mPendingInsets.equals( + mInsetsController.getState()); final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets); final boolean surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; @@ -2230,6 +2263,10 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar; contentInsetsChanged = true; } + if (insetsStateChanged) { + mInsetsController.setState(mPendingInsets); + contentInsetsChanged = true; + } if (contentInsetsChanged || mLastSystemUiVisibility != mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested || mLastOverscanRequested != mAttachInfo.mOverscanRequested @@ -2675,7 +2712,6 @@ public final class ViewRootImpl implements ViewParent, } private void maybeHandleWindowMove(Rect frame) { - // TODO: Well, we are checking whether the frame has changed similarly // to how this is done for the insets. This is however incorrect since // the insets and the frame are translated. For example, the old frame @@ -4180,6 +4216,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_UPDATE_POINTER_ICON = 27; private final static int MSG_POINTER_CAPTURE_CHANGED = 28; private final static int MSG_DRAW_FINISHED = 29; + private final static int MSG_INSETS_CHANGED = 30; final class ViewRootHandler extends Handler { @Override @@ -4235,6 +4272,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_POINTER_CAPTURE_CHANGED"; case MSG_DRAW_FINISHED: return "MSG_DRAW_FINISHED"; + case MSG_INSETS_CHANGED: + return "MSG_INSETS_CHANGED"; } return super.getMessageName(message); } @@ -4315,7 +4354,7 @@ public final class ViewRootImpl implements ViewParent, || !mPendingVisibleInsets.equals(args.arg3) || !mPendingOutsets.equals(args.arg7); - mWinFrame.set((Rect) args.arg1); + setFrame((Rect) args.arg1); mPendingOverscanInsets.set((Rect) args.arg5); mPendingContentInsets.set((Rect) args.arg2); mPendingStableInsets.set((Rect) args.arg6); @@ -4338,16 +4377,25 @@ public final class ViewRootImpl implements ViewParent, requestLayout(); } break; + case MSG_INSETS_CHANGED: + mPendingInsets = (InsetsState) msg.obj; + + // TODO: Full traversal not needed here + if (USE_NEW_INSETS) { + requestLayout(); + } + break; case MSG_WINDOW_MOVED: if (mAdded) { final int w = mWinFrame.width(); final int h = mWinFrame.height(); final int l = msg.arg1; final int t = msg.arg2; - mWinFrame.left = l; - mWinFrame.right = l + w; - mWinFrame.top = t; - mWinFrame.bottom = t + h; + mTmpFrame.left = l; + mTmpFrame.right = l + w; + mTmpFrame.top = t; + mTmpFrame.bottom = t + h; + setFrame(mTmpFrame); mPendingBackDropFrame.set(mWinFrame); maybeHandleWindowMove(mWinFrame); @@ -6733,9 +6781,9 @@ public final class ViewRootImpl implements ViewParent, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, - mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, + mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurface); + mPendingMergedConfiguration, mSurface, mPendingInsets); mPendingAlwaysConsumeNavBar = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0; @@ -6745,15 +6793,22 @@ public final class ViewRootImpl implements ViewParent, } if (mTranslator != null) { - mTranslator.translateRectInScreenToAppWinFrame(mWinFrame); + mTranslator.translateRectInScreenToAppWinFrame(mTmpFrame); mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets); mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets); mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets); mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets); } + setFrame(mTmpFrame); + return relayoutResult; } + private void setFrame(Rect frame) { + mWinFrame.set(frame); + mInsetsController.onFrameChanged(frame); + } + /** * {@inheritDoc} */ @@ -6856,6 +6911,8 @@ public final class ViewRootImpl implements ViewParent, mChoreographer.dump(prefix, writer); + mInsetsController.dump(prefix, writer); + writer.print(prefix); writer.println("View Hierarchy:"); dumpViewHierarchy(innerPrefix, writer, mView); } @@ -7064,6 +7121,10 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } + private void dispatchInsetsChanged(InsetsState insetsState) { + mHandler.obtainMessage(MSG_INSETS_CHANGED, insetsState).sendToTarget(); + } + public void dispatchMoved(int newX, int newY) { if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY); if (mTranslator != null) { @@ -8126,6 +8187,14 @@ public final class ViewRootImpl implements ViewParent, } } + @Override + public void insetsChanged(InsetsState insetsState) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchInsetsChanged(insetsState); + } + } + @Override public void moved(int newX, int newY) { final ViewRootImpl viewAncestor = mViewAncestor.get(); diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index b9d53c1b5884a..d78bfac1f8788 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -120,6 +120,8 @@ public final class SomeArgs { arg5 = null; arg6 = null; arg7 = null; + arg8 = null; + arg9 = null; argi1 = 0; argi2 = 0; argi3 = 0; diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 137ca7f2ac273..36fe4fc5af49f 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -27,6 +27,7 @@ import android.view.DragEvent; import android.view.IWindow; import android.view.IWindowSession; import android.view.PointerIcon; +import android.view.InsetsState; import com.android.internal.os.IResultReceiver; @@ -52,6 +53,10 @@ public class BaseIWindow extends IWindow.Stub { } } + @Override + public void insetsChanged(InsetsState insetsState) { + } + @Override public void moved(int newX, int newY) { } diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java new file mode 100644 index 0000000000000..ed472d2a7f64b --- /dev/null +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 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 static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static junit.framework.Assert.assertEquals; + +import android.graphics.Insets; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class InsetsSourceTest { + + private InsetsSource mSource = new InsetsSource(TYPE_NAVIGATION_BAR); + + @Before + public void setUp() { + mSource.setVisible(true); + } + + @Test + public void testCalculateInsetsTop() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + Insets insets = mSource.calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test + public void testCalculateInsetsBottom() { + mSource.setFrame(new Rect(0, 400, 500, 500)); + Insets insets = mSource.calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(0, 0, 0, 100), insets); + } + + @Test + public void testCalculateInsetsLeft() { + mSource.setFrame(new Rect(0, 0, 100, 500)); + Insets insets = mSource.calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(100, 0, 0, 0), insets); + } + + @Test + public void testCalculateInsetsRight() { + mSource.setFrame(new Rect(400, 0, 500, 500)); + Insets insets = mSource.calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(0, 0, 100, 0), insets); + } + + @Test + public void testCalculateInsets_overextend() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + Insets insets = mSource.calculateInsets(new Rect(100, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test + public void testCalculateInsets_invisible() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisible(false); + Insets insets = mSource.calculateInsets(new Rect(100, 0, 500, 500), + false /* ignoreVisibility */); + assertEquals(Insets.of(0, 0, 0, 0), insets); + } + + @Test + public void testCalculateInsets_ignoreVisibility() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisible(false); + Insets insets = mSource.calculateInsets(new Rect(100, 0, 500, 500), + true /* ignoreVisibility */); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + // Parcel and equals already tested via InsetsStateTest +} diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java new file mode 100644 index 0000000000000..6bb9539e89bde --- /dev/null +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 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 static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.graphics.Rect; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class InsetsStateTest { + + private InsetsState mState = new InsetsState(); + private InsetsState mState2 = new InsetsState(); + + @Test + public void testCalculateInsets() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_TOP_BAR).setVisible(true); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(TYPE_IME).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT); + assertEquals(new Rect(0, 100, 0, 100), insets.getSystemWindowInsets()); + } + + @Test + public void testCalculateInsets_imeAndNav() { + mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(TYPE_IME).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT); + assertEquals(100, insets.getStableInsetBottom()); + assertEquals(new Rect(0, 0, 0, 200), insets.getSystemWindowInsets()); + } + + @Test + public void testCalculateInsets_navRightStatusTop() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_TOP_BAR).setVisible(true); + mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); + mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT); + assertEquals(new Rect(0, 100, 20, 0), insets.getSystemWindowInsets()); + } + + @Test + public void testStripForDispatch() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_TOP_BAR).setVisible(true); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(TYPE_IME).setVisible(true); + mState.removeSource(TYPE_IME); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT); + assertEquals(0, insets.getSystemWindowInsetBottom()); + } + + @Test + public void testEquals_differentRect() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 10, 10)); + assertNotEquals(mState, mState2); + } + + @Test + public void testEquals_differentSource() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + assertNotEquals(mState, mState2); + } + + @Test + public void testEquals_sameButDifferentInsertOrder() { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + assertEquals(mState, mState2); + } + + @Test + public void testEquals_visibility() { + mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_IME).setVisible(true); + mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + assertNotEquals(mState, mState2); + } + + @Test + public void testParcelUnparcel() { + mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_IME).setVisible(true); + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + Parcel p = Parcel.obtain(); + mState.writeToParcel(p, 0 /* flags */); + mState2.readFromParcel(p); + p.recycle(); + assertEquals(mState, mState2); + } +} diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java index de110c8493387..d9da27c8b931c 100644 --- a/graphics/java/android/graphics/Insets.java +++ b/graphics/java/android/graphics/Insets.java @@ -81,6 +81,17 @@ public final class Insets { return new Rect(left, top, right, bottom); } + /** + * Add two Insets. + * + * @param a The first Insets to add. + * @param b The second Insets to add. + * @return a + b, i. e. all insets on every side are added together. + */ + public static @NonNull Insets add(@NonNull Insets a, @NonNull Insets b) { + return Insets.of(a.left + b.left, a.top + b.top, a.right + b.right, a.bottom + b.bottom); + } + /** * Two Insets instances are equal iff they belong to the same class and their fields are * pairwise equal. diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index c4dc0adb3be04..40a32f3429dc4 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -105,6 +105,20 @@ public final class Rect implements Parcelable { } } + /** + * @hide + */ + public Rect(@Nullable Insets r) { + if (r == null) { + left = top = right = bottom = 0; + } else { + left = r.left; + top = r.top; + right = r.right; + bottom = r.bottom; + } + } + /** * Returns a copy of {@code r} if {@code r} is not {@code null}, or {@code null} otherwise. * @@ -417,6 +431,18 @@ public final class Rect implements Parcelable { bottom -= insets.bottom; } + /** + * Insets the rectangle on all sides specified by the dimensions of {@code insets}. + * @hide + * @param insets The insets to inset the rect by. + */ + public void inset(Insets insets) { + left += insets.left; + top += insets.top; + right -= insets.right; + bottom -= insets.bottom; + } + /** * Insets the rectangle on all sides specified by the insets. * @hide diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3acacbcce71d8..c546ac7111516 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -34,6 +34,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; +import static android.view.InsetsState.TYPE_IME; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -124,6 +125,7 @@ import android.animation.AnimationHandler; import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -151,6 +153,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.InputChannel; import android.view.InputDevice; +import android.view.InsetsState.InternalInsetType; import android.view.MagnificationSpec; import android.view.Surface; import android.view.SurfaceControl; @@ -162,6 +165,7 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; +import com.android.internal.util.function.TriConsumer; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.DisplayRotationUtil; import com.android.server.wm.utils.RotationCache; @@ -515,6 +519,8 @@ class DisplayContent extends WindowContainer frameProvider) { + mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider); + } + + InsetsStateController getInsetsStateController() { + return mInsetsStateController; + } + @VisibleForTesting void setDisplayRotation(DisplayRotation displayRotation) { mDisplayRotation = displayRotation; @@ -2733,6 +2757,8 @@ class DisplayContent extends WindowContainer { + rect.top = 0; + rect.bottom = getStatusBarHeight(displayFrames); + }); break; case TYPE_NAVIGATION_BAR: mContext.enforceCallingOrSelfPermission( @@ -818,6 +825,8 @@ public class DisplayPolicy { mNavigationBarController.setWindow(win); mNavigationBarController.setOnBarVisibilityChangedListener( mNavBarVisibilityListener, true); + mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR, + win, null /* frameProvider */); if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: @@ -845,9 +854,11 @@ public class DisplayPolicy { if (mDisplayContent.isDefaultDisplay) { mService.mPolicy.setKeyguardCandidateLw(null); } + mDisplayContent.setInsetProvider(TYPE_TOP_BAR, null, null); } else if (mNavigationBar == win) { mNavigationBar = null; mNavigationBarController.setWindow(null); + mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR, null, null); } if (mLastFocusedWindow == win) { mLastFocusedWindow = null; @@ -855,6 +866,11 @@ public class DisplayPolicy { mScreenDecorWindows.remove(win); } + private int getStatusBarHeight(DisplayFrames displayFrames) { + return Math.max(mStatusBarHeightForRotation[displayFrames.mRotation], + displayFrames.mDisplayCutoutSafe.top); + } + /** * Control the animation to run when a window's state changes. Return a * non-0 number to force the animation to a specific resource ID, or 0 diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java new file mode 100644 index 0000000000000..e96f0b1c4416c --- /dev/null +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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.server.wm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.InsetsSource; + +import com.android.internal.util.function.TriConsumer; +import com.android.server.policy.WindowManagerPolicy; + +/** + * Controller for a specific inset source on the server. It's called provider as it provides the + * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. + */ +class InsetsSourceProvider { + + private final Rect mTmpRect = new Rect(); + private final @NonNull InsetsSource mSource; + private WindowState mWin; + private TriConsumer mFrameProvider; + + InsetsSourceProvider(InsetsSource source) { + mSource = source; + } + + InsetsSource getSource() { + return mSource; + } + + /** + * Updates the window that currently backs this source. + * + * @param win The window that links to this source. + * @param frameProvider Based on display frame state and the window, calculates the resulting + * frame that should be reported to clients. + */ + void setWindow(@Nullable WindowState win, + @Nullable TriConsumer frameProvider) { + if (mWin != null) { + mWin.setInsetProvider(null); + } + mWin = win; + mFrameProvider = frameProvider; + if (win == null) { + mSource.setVisible(false); + mSource.setFrame(new Rect()); + } else { + mSource.setVisible(true); + mWin.setInsetProvider(this); + } + } + + /** + * Called when a layout pass has occurred. + */ + void onPostLayout() { + if (mWin == null) { + return; + } + + mTmpRect.set(mWin.getFrameLw()); + if (mFrameProvider != null) { + mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect); + } else { + mTmpRect.inset(mWin.mGivenContentInsets); + } + mSource.setFrame(mTmpRect); + mSource.setVisible(mWin.isVisible() && !mWin.mGivenInsetsPending); + + } +} diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java new file mode 100644 index 0000000000000..1189ee6606050 --- /dev/null +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 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.server.wm; + +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; + +import android.util.ArrayMap; +import android.view.InsetsState; + +import java.io.PrintWriter; +import java.util.function.Consumer; + +/** + * Manages global window inset state in the system represented by {@link InsetsState}. + */ +class InsetsStateController { + + private final InsetsState mLastState = new InsetsState(); + private final InsetsState mState = new InsetsState(); + private final DisplayContent mDisplayContent; + private ArrayMap mControllers = new ArrayMap<>(); + + private final Consumer mDispatchInsetsChanged = w -> { + if (w.isVisible()) { + w.notifyInsetsChanged(); + } + }; + + InsetsStateController(DisplayContent displayContent) { + mDisplayContent = displayContent; + } + + /** + * When dispatching window state to the client, we'll need to exclude the source that represents + * the window that is being dispatched. + * + * @param target The client we dispatch the state to. + * @return The state stripped of the necessary information. + */ + InsetsState getInsetsForDispatch(WindowState target) { + final InsetsSourceProvider provider = target.getInsetProvider(); + if (provider == null) { + return mState; + } + + final InsetsState state = new InsetsState(); + state.set(mState); + final int type = provider.getSource().getType(); + state.removeSource(type); + + // Navigation bar doesn't get influenced by anything else + if (type == TYPE_NAVIGATION_BAR) { + state.removeSource(TYPE_IME); + state.removeSource(TYPE_TOP_BAR); + } + return state; + } + + /** + * @return The provider of a specific type. + */ + InsetsSourceProvider getSourceProvider(int type) { + return mControllers.computeIfAbsent(type, + key -> new InsetsSourceProvider(mState.getSource(key))); + } + + /** + * Called when a layout pass has occurred. + */ + void onPostLayout() { + for (int i = mControllers.size() - 1; i>= 0; i--) { + mControllers.valueAt(i).onPostLayout(); + } + if (!mLastState.equals(mState)) { + mLastState.set(mState, true /* copySources */); + notifyInsetsChanged(); + } + } + + private void notifyInsetsChanged() { + mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */); + } + + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "WindowInsetsStateController"); + mState.dump(prefix + " ", pw); + } +} diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 6838c55100e86..37b5a7c302187 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -50,6 +50,7 @@ import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.view.InsetsState; import android.view.WindowManager; import com.android.internal.os.logging.MetricsLoggerWrapper; @@ -153,17 +154,21 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, - DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) { + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, - outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel); + outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel, + outInsetsState); } @Override public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets) { + int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, + InsetsState outInsetsState) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, new Rect() /* outFrame */, outContentInsets, outStableInsets, null /* outOutsets */, - new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */); + new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */, + outInsetsState); } @Override @@ -182,7 +187,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - Surface outSurface) { + Surface outSurface, InsetsState outInsetsState) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -190,7 +195,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, outsets, outBackdropFrame, cutout, - mergedConfiguration, outSurface); + mergedConfiguration, outSurface, outInsetsState); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index a7b0272d4b0d3..7dc509aff024b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -65,6 +65,7 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; import android.view.ViewGroup.LayoutParams; +import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -141,6 +142,7 @@ class TaskSnapshotSurface implements StartingSurface { final Rect taskBounds; final Rect tmpContentInsets = new Rect(); final Rect tmpStableInsets = new Rect(); + final InsetsState mTmpInsetsState = new InsetsState(); final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); int backgroundColor = WHITE; int statusBarColor = 0; @@ -201,7 +203,7 @@ class TaskSnapshotSurface implements StartingSurface { try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, View.GONE, token.getDisplayContent().getDisplayId(), tmpFrame, tmpRect, tmpRect, - tmpRect, tmpCutout, null); + tmpRect, tmpCutout, null, mTmpInsetsState); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); return null; @@ -217,7 +219,7 @@ class TaskSnapshotSurface implements StartingSurface { try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect, - tmpCutout, tmpMergedConfiguration, surface); + tmpCutout, tmpMergedConfiguration, surface, mTmpInsetsState); } catch (RemoteException e) { // Local call. } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 25f31280da6cb..9017cca173cc5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -218,6 +218,7 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; import android.view.WindowContentFrameStats; +import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.RemoveContentMode; @@ -1111,7 +1112,8 @@ public class WindowManagerService extends IWindowManager.Stub public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, - DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) { + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { @@ -1459,6 +1461,7 @@ public class WindowManagerService extends IWindowManager.Stub outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR; } + outInsetsState.set(displayContent.getInsetsStateController().getInsetsForDispatch(win)); if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; @@ -1856,7 +1859,7 @@ public class WindowManagerService extends IWindowManager.Stub long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, - Surface outSurface) { + Surface outSurface, InsetsState outInsetsState) { int result = 0; boolean configChanged; final boolean hasStatusBarPermission = @@ -2157,6 +2160,7 @@ public class WindowManagerService extends IWindowManager.Stub outStableInsets, outOutsets); outCutout.set(win.getWmDisplayCutout().getDisplayCutout()); outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw())); + outInsetsState.set(displayContent.getInsetsStateController().getInsetsForDispatch(win)); if (localLOGV) Slog.v( TAG_WM, "Relayout given client " + client.asBinder() + ", requestedWidth=" + requestedWidth diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6f044f3d26890..60a17e7c1f40f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -143,6 +143,7 @@ import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; import android.annotation.CallSuper; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.content.res.Configuration; @@ -194,6 +195,7 @@ import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.policy.WindowManagerPolicy.DisplayContentInfo; import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; import com.android.server.wm.utils.InsetUtils; import com.android.server.wm.utils.WmDisplayCutout; @@ -578,6 +580,8 @@ class WindowState extends WindowContainer implements WindowManagerP */ private boolean mIsDimming = false; + private @Nullable InsetsSourceProvider mInsetProvider; + private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, @@ -2945,6 +2949,18 @@ class WindowState extends WindowContainer implements WindowManagerP Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + /** + * Called when the insets state changed. + */ + void notifyInsetsChanged() { + try { + mClient.insetsChanged( + getDisplayContent().getInsetsStateController().getInsetsForDispatch(this)); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset state change", e); + } + } + Rect getBackdropFrame(Rect frame) { // When the task is docked, we send fullscreen sized backDropFrame as soon as resizing // start even if we haven't received the relayout window, so that the client requests @@ -4766,6 +4782,14 @@ class WindowState extends WindowContainer implements WindowManagerP mWindowFrames.setContentChanged(false); } + void setInsetProvider(InsetsSourceProvider insetProvider) { + mInsetProvider = insetProvider; + } + + InsetsSourceProvider getInsetProvider() { + return mInsetProvider; + } + private final class MoveAnimationSpec implements AnimationSpec { private final long mDuration; diff --git a/services/tests/servicestests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/servicestests/src/com/android/server/wm/InsetsSourceProviderTest.java new file mode 100644 index 0000000000000..241b987e58ba6 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.server.wm; + +import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static org.junit.Assert.assertEquals; + +import android.graphics.Insets; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.InsetsSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@FlakyTest(detail = "Promote once confirmed non-flaky") +@Presubmit +public class InsetsSourceProviderTest extends WindowTestsBase { + + private InsetsSourceProvider mProvider = new InsetsSourceProvider( + new InsetsSource(TYPE_TOP_BAR)); + + @Test + public void testPostLayout() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + topBar.getFrameLw().set(0, 0, 500, 100); + topBar.mHasSurface = true; + mProvider.setWindow(topBar, null); + mProvider.onPostLayout(); + assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame()); + assertEquals(Insets.of(0, 100, 0, 0), + mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */)); + } + + @Test + public void testPostLayout_invisible() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + topBar.getFrameLw().set(0, 0, 500, 100); + mProvider.setWindow(topBar, null); + mProvider.onPostLayout(); + assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */)); + } + + @Test + public void testPostLayout_frameProvider() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + topBar.getFrameLw().set(0, 0, 500, 100); + mProvider.setWindow(topBar, + (displayFrames, windowState, rect) -> { + rect.set(10, 10, 20, 20); + }); + mProvider.onPostLayout(); + assertEquals(new Rect(10, 10, 20, 20), mProvider.getSource().getFrame()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/InsetsStateControllerTest.java new file mode 100644 index 0000000000000..7505db103bbf8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.server.wm; + +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.platform.test.annotations.Presubmit; +import android.view.InsetsState; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@FlakyTest(detail = "Promote once confirmed non-flaky") +@Presubmit +public class InsetsStateControllerTest extends WindowTestsBase { + + @Test + public void testStripForDispatch_notOwn() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow"); + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) + .setWindow(topBar, null); + topBar.setInsetProvider( + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)); + assertNotNull(mDisplayContent.getInsetsStateController().getInsetsForDispatch(app) + .getSource(TYPE_TOP_BAR)); + } + + @Test + public void testStripForDispatch_own() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) + .setWindow(topBar, null); + topBar.setInsetProvider( + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)); + assertEquals(new InsetsState(), + mDisplayContent.getInsetsStateController().getInsetsForDispatch(topBar)); + } + + @Test + public void testStripForDispatch_navBar() { + final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState ime = createWindow(null, TYPE_APPLICATION, "parentWindow"); + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) + .setWindow(topBar, null); + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_NAVIGATION_BAR) + .setWindow(navBar, null); + mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_IME) + .setWindow(ime, null); + assertEquals(new InsetsState(), + mDisplayContent.getInsetsStateController().getInsetsForDispatch(navBar)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java index 99deeb9e9397b..432af0d7a469d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java @@ -24,6 +24,7 @@ import android.util.MergedConfiguration; import android.view.DisplayCutout; import android.view.DragEvent; import android.view.IWindow; +import android.view.InsetsState; import com.android.internal.os.IResultReceiver; @@ -39,6 +40,9 @@ public class TestIWindow extends IWindow.Stub { Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, DisplayCutout.ParcelableWrapper displayCutout) throws RemoteException { } + @Override + public void insetsChanged(InsetsState insetsState) throws RemoteException { + } @Override public void moved(int newX, int newY) throws RemoteException { diff --git a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java index ae3914ebf162d..d5987a5373b49 100644 --- a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java +++ b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java @@ -26,6 +26,7 @@ import android.util.MergedConfiguration; import android.view.Display; import android.view.DisplayCutout; import android.view.IWindowSession; +import android.view.InsetsState; import android.view.Surface; import android.view.View; import android.view.WindowManager; @@ -105,7 +106,7 @@ public class MainActivity extends Activity { window.mSeq, mLayoutParams, -1, -1, View.VISIBLE, 0, -1, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, new DisplayCutout.ParcelableWrapper(), new MergedConfiguration(), - new Surface()); + new Surface(), new InsetsState()); } catch (RemoteException e) { e.printStackTrace(); } @@ -131,8 +132,9 @@ public class MainActivity extends Activity { final IWindowSession session = WindowManagerGlobal.getWindowSession(); final Rect tmpRect = new Rect(); try { - final int res = session.addToDisplayWithoutInputChannel(window, window.mSeq, layoutParams, - View.VISIBLE, Display.DEFAULT_DISPLAY, tmpRect, tmpRect); + final int res = session.addToDisplayWithoutInputChannel(window, window.mSeq, + layoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, tmpRect, tmpRect, + new InsetsState()); } catch (RemoteException e) { e.printStackTrace(); }