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
diff --git a/api/current.txt b/api/current.txt
index 115c1c1..2ba2f7a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13940,6 +13940,7 @@
}
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 f78506b..912953e 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -1461,7 +1461,6 @@
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 f6bb762..45d53f3 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -56,6 +56,7 @@
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 @@
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 @@
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 @@
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 4b8b7f3..af41b69 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -24,6 +24,7 @@
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 @@
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 bedfa9f..9762586 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -28,6 +28,7 @@
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 @@
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 @@
* 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 @@
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 0000000..7841d04
--- /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 0000000..0cb8ad7
--- /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<InsetsSource> CREATOR = new Creator<InsetsSource>() {
+
+ 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 0000000..d02ddd1
--- /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 0000000..9895adc
--- /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<Integer, InsetsSource> 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<InsetsState> CREATOR = new Creator<InsetsState>() {
+
+ 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 484c6f3..872f147 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -161,6 +161,19 @@
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.
+ * <p>
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
|| !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 @@
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 @@
(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 @@
}
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 @@
mChoreographer.dump(prefix, writer);
+ mInsetsController.dump(prefix, writer);
+
writer.print(prefix); writer.println("View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
@@ -7064,6 +7121,10 @@
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) {
@@ -8127,6 +8188,14 @@
}
@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();
if (viewAncestor != null) {
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index b9d53c1..d78bfac 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -120,6 +120,8 @@
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 137ca7f..36fe4fc 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.IWindow;
import android.view.IWindowSession;
import android.view.PointerIcon;
+import android.view.InsetsState;
import com.android.internal.os.IResultReceiver;
@@ -53,6 +54,10 @@
}
@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 0000000..ed472d2
--- /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 0000000..6bb9539
--- /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 de110c8..d9da27c 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -82,6 +82,17 @@
}
/**
+ * 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 c4dc0ad..40a32f3 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -106,6 +106,20 @@
}
/**
+ * @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.
*
* @hide
@@ -418,6 +432,18 @@
}
/**
+ * 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
* @param left The amount to add from the rectangle's left
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3acacbc..c546ac7 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_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.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.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 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 @@
private final PointerEventDispatcher mPointerEventDispatcher;
+ private final InsetsStateController mInsetsStateController;
+
// Last systemUiVisibility we received from status bar.
private int mLastStatusBarVisibility = 0;
// Last systemUiVisibility we dispatched to windows.
@@ -922,6 +928,7 @@
mService.mAnimator.addDisplayLocked(mDisplayId);
mInputMonitor = new InputMonitor(service, mDisplayId);
+ mInsetsStateController = new InsetsStateController(this);
}
boolean isReady() {
@@ -1058,6 +1065,23 @@
return mDisplayRotation;
}
+ /**
+ * Marks a window as providing insets for the rest of the windows in the system.
+ *
+ * @param type The type of inset this window provides.
+ * @param win The window.
+ * @param frameProvider Function to compute the frame, or {@code null} if the just the frame of
+ * the window should be taken.
+ */
+ void setInsetProvider(@InternalInsetType int type, WindowState win,
+ @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
+ mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
+ }
+
+ InsetsStateController getInsetsStateController() {
+ return mInsetsStateController;
+ }
+
@VisibleForTesting
void setDisplayRotation(DisplayRotation displayRotation) {
mDisplayRotation = displayRotation;
@@ -2733,6 +2757,8 @@
mDisplayRotation.dump(prefix, pw);
pw.println();
mInputMonitor.dump(pw, " ");
+ pw.println();
+ mInsetsStateController.dump(prefix, pw);
}
@Override
@@ -3015,6 +3041,8 @@
mInputMethodWindow.getDisplayId());
}
computeImeTarget(true /* updateImeTarget */);
+ mInsetsStateController.getSourceProvider(TYPE_IME).setWindow(win,
+ null /* frameProvider */);
}
/**
@@ -3470,6 +3498,7 @@
pendingLayoutChanges |= mDisplayPolicy.finishPostLayoutPolicyLw();
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
"after finishPostLayoutPolicyLw", pendingLayoutChanges);
+ mInsetsStateController.onPostLayout();
} while (pendingLayoutChanges != 0);
mTmpApplySurfaceChangesTransactionState.reset();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c16f95e..0e5947a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.view.InsetsState.TYPE_TOP_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -128,6 +129,7 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
@@ -804,6 +806,11 @@
if (mDisplayContent.isDefaultDisplay) {
mService.mPolicy.setKeyguardCandidateLw(win);
}
+ mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win,
+ (displayFrames, windowState, rect) -> {
+ rect.top = 0;
+ rect.bottom = getStatusBarHeight(displayFrames);
+ });
break;
case TYPE_NAVIGATION_BAR:
mContext.enforceCallingOrSelfPermission(
@@ -818,6 +825,8 @@
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 @@
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 @@
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 0000000..e96f0b1
--- /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<DisplayFrames, WindowState, Rect> 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<DisplayFrames, WindowState, Rect> 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 0000000..1189ee6
--- /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<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>();
+
+ private final Consumer<WindowState> 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 6838c55..37b5a7c 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.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 @@
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 @@
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 @@
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 a7b0272..7dc509a 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.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 @@
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 @@
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 @@
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 25f3128..9017cca 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.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 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 @@
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 @@
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 @@
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 6f044f3..60a17e7 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_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 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 @@
*/
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 @@
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 @@
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 0000000..241b987
--- /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 0000000..7505db1
--- /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 99deeb9..432af0d 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.view.DisplayCutout;
import android.view.DragEvent;
import android.view.IWindow;
+import android.view.InsetsState;
import com.android.internal.os.IResultReceiver;
@@ -39,6 +40,9 @@
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 ae3914e..d5987a5 100644
--- a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
+++ b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
@@ -26,6 +26,7 @@
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 @@
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 @@
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();
}