diff options
| author | 2018-11-29 17:14:15 +0000 | |
|---|---|---|
| committer | 2018-11-29 17:14:15 +0000 | |
| commit | 9a94afc6ca274c73f91a05bdaa4a9a8b1d9eba0f (patch) | |
| tree | 75eb93d64bbb631e8bd10965c7c2e4c95e4367b5 | |
| parent | fe46ecd6adf107eaaf5ccff3c44f52faea4e0182 (diff) | |
| parent | f96c90ac6c4e12113b5d0187bf3be9b39e7027f4 (diff) | |
Merge "A brave new world for window insets (1/n)"
28 files changed, 1304 insertions, 35 deletions
diff --git a/api/current.txt b/api/current.txt index d1560edf2782..9d46863f0102 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13945,6 +13945,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 0277b7e3e71a..25bd0330ad5c 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 f6bb762a0bef..45d53f340f35 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 4b8b7f304b0f..af41b6942a5e 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 bedfa9ff133c..97625869209d 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 000000000000..7841d0417a2b --- /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 000000000000..0cb8ad72f102 --- /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 000000000000..d02ddd15a8c9 --- /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 000000000000..9895adcad23a --- /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 8e9f2d75bda0..d9d52c01924f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -161,6 +161,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. + * <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 @@ 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; @@ -1771,7 +1792,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) || @@ -1788,10 +1810,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; } @@ -1991,6 +2019,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; @@ -2184,6 +2215,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; @@ -2221,6 +2254,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 @@ -2666,7 +2703,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 @@ -4171,6 +4207,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 @@ -4226,6 +4263,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); } @@ -4306,7 +4345,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); @@ -4329,16 +4368,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); @@ -6724,9 +6772,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; @@ -6736,15 +6784,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} */ @@ -6847,6 +6902,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); } @@ -7055,6 +7112,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) { @@ -8118,6 +8179,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(); if (viewAncestor != null) { diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index b9d53c1b5884..d78bfac1f878 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 137ca7f2ac27..36fe4fc5af49 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; @@ -53,6 +54,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 000000000000..ed472d2a7f64 --- /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 000000000000..6bb9539e89bd --- /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 de110c849338..d9da27c8b931 100644 --- a/graphics/java/android/graphics/Insets.java +++ b/graphics/java/android/graphics/Insets.java @@ -82,6 +82,17 @@ public final class Insets { } /** + * 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 c4dc0adb3be0..40a32f3429dc 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -106,6 +106,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. * * @hide @@ -418,6 +432,18 @@ public final class Rect implements Parcelable { } /** + * 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 a834ef16f426..c0e983653b27 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; @@ -123,6 +124,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; @@ -150,6 +152,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; @@ -161,6 +164,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; @@ -506,6 +510,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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. @@ -902,6 +908,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mService.mAnimator.addDisplayLocked(mDisplayId); mInputMonitor = new InputMonitor(service, mDisplayId); + mInsetsStateController = new InsetsStateController(this); } boolean isReady() { @@ -1038,6 +1045,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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; @@ -2712,6 +2736,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplayRotation.dump(prefix, pw); pw.println(); mInputMonitor.dump(pw, " "); + pw.println(); + mInsetsStateController.dump(prefix, pw); } @Override @@ -2994,6 +3020,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodWindow.getDisplayId()); } computeImeTarget(true /* updateImeTarget */); + mInsetsStateController.getSourceProvider(TYPE_IME).setWindow(win, + null /* frameProvider */); } /** @@ -3449,6 +3477,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 c16f95ee1160..0e5947af0c61 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_SPLIT_SCREEN_SECOND 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.InputChannel; 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 @@ public class DisplayPolicy { 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 @@ 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 000000000000..e96f0b1c4416 --- /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 000000000000..1189ee660605 --- /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 6838c55100e8..37b5a7c30218 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 8f02f8aa5dd9..9a56606aee7f 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -66,6 +66,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; @@ -142,6 +143,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; @@ -202,7 +204,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; @@ -218,7 +220,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 5df34517956a..b91afcd18e45 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 91c328809adb..cfd1f86cdeaa 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<WindowState> 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, @@ -2955,6 +2959,18 @@ class WindowState extends WindowContainer<WindowState> 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 @@ -4776,6 +4792,14 @@ class WindowState extends WindowContainer<WindowState> 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 000000000000..241b987e58ba --- /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 000000000000..7505db103bbf --- /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 99deeb9e9397..432af0d7a469 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 ae3914ebf162..d5987a5373b4 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(); } |