Set insets on the virtual display to avoid IME covering the bubble.
Bug: 123544535
Test: Manual test using the test app, and atest DisplayPolicyLayoutTests
Change-Id: If2fceea97f4d702d000d887883c7f131337e9fd0
diff --git a/api/current.txt b/api/current.txt
index f8a164d..7ead41c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14191,13 +14191,16 @@
field public static final int YV12 = 842094169; // 0x32315659
}
- public final class Insets {
+ public final class Insets implements android.os.Parcelable {
method @NonNull public static android.graphics.Insets add(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+ method public int describeContents();
method @NonNull public static android.graphics.Insets max(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
method @NonNull public static android.graphics.Insets min(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
method @NonNull public static android.graphics.Insets of(int, int, int, int);
method @NonNull public static android.graphics.Insets of(@Nullable android.graphics.Rect);
method @NonNull public static android.graphics.Insets subtract(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.Insets> CREATOR;
field public static final android.graphics.Insets NONE;
field public final int bottom;
field public final int left;
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 5814e69..ce71998 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -26,6 +26,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Insets;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.RemoteException;
@@ -84,6 +85,8 @@
/** The ActivityView is only allowed to contain one task. */
private final boolean mSingleTaskInstance;
+ private Insets mForwardedInsets;
+
@UnsupportedAppUsage
public ActivityView(Context context) {
this(context, null /* attrs */);
@@ -369,11 +372,13 @@
.build();
try {
+ // TODO: Find a way to consolidate these calls to the server.
wm.reparentDisplayContent(displayId, mRootSurfaceControl);
wm.dontOverrideDisplayInfo(displayId);
if (mSingleTaskInstance) {
mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
}
+ wm.setForwardedInsets(displayId, mForwardedInsets);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -454,6 +459,24 @@
}
/**
+ * Set forwarded insets on the virtual display.
+ *
+ * @see IWindowManager#setForwardedInsets
+ */
+ public void setForwardedInsets(Insets insets) {
+ mForwardedInsets = insets;
+ if (mVirtualDisplay == null) {
+ return;
+ }
+ try {
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* A task change listener that detects background color change of the topmost stack on our
* virtual display and updates the background of the surface view. This background will be shown
* when surface view is resized, but the app hasn't drawn its content in new size yet.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8ae4757..2ef7c4b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -380,6 +381,16 @@
void getStableInsets(int displayId, out Rect outInsets);
/**
+ * Set the forwarded insets on the display.
+ * <p>
+ * This is only used in case a virtual display is displayed on another display that has insets,
+ * and the bounds of the virtual display is overlapping with the insets from the host display.
+ * In that case, the contents on the virtual display won't be placed over the forwarded insets.
+ * Only the owner of the display is permitted to set the forwarded insets on it.
+ */
+ void setForwardedInsets(int displayId, in Insets insets);
+
+ /**
* Register shortcut key. Shortcut code is packed as:
* (MetaState << Integer.SIZE) | KeyCode
* @hide
diff --git a/graphics/java/android/graphics/Insets.aidl b/graphics/java/android/graphics/Insets.aidl
new file mode 100644
index 0000000..e65e72d
--- /dev/null
+++ b/graphics/java/android/graphics/Insets.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/graphics/Insets.aidl
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.graphics;
+
+parcelable Insets;
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
index 8258b57..c64c789 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
/**
* An Insets instance holds four integer offsets which describe changes to the four
@@ -27,7 +29,7 @@
* Insets are immutable so may be treated as values.
*
*/
-public final class Insets {
+public final class Insets implements Parcelable {
public static final Insets NONE = new Insets(0, 0, 0, 0);
public final int left;
@@ -73,7 +75,7 @@
}
/**
- * Returns a Rect intance with the appropriate values.
+ * Returns a Rect instance with the appropriate values.
*
* @hide
*/
@@ -168,4 +170,29 @@
", bottom=" + bottom +
'}';
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(left);
+ out.writeInt(top);
+ out.writeInt(right);
+ out.writeInt(bottom);
+ }
+
+ public static final Parcelable.Creator<Insets> CREATOR = new Parcelable.Creator<Insets>() {
+ @Override
+ public Insets createFromParcel(Parcel in) {
+ return new Insets(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+ }
+
+ @Override
+ public Insets[] newArray(int size) {
+ return new Insets[size];
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 2c23c0c..74ddc8f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -22,6 +22,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -32,6 +34,7 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -266,6 +269,24 @@
}
}
});
+ mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
+ ActivityView activityView = (ActivityView) view;
+ // Here we assume that the position of the ActivityView on the screen
+ // remains regardless of IME status. When we move ActivityView, the
+ // forwardedInsets should be computed not against the current location
+ // and size, but against the post-moved location and size.
+ Point displaySize = new Point();
+ view.getContext().getDisplay().getSize(displaySize);
+ int[] windowLocation = view.getLocationOnScreen();
+ final int windowBottom = windowLocation[1] + view.getHeight();
+ final int keyboardHeight = insets.getSystemWindowInsetBottom()
+ - insets.getStableInsetBottom();
+ final int insetsBottom = Math.max(0,
+ windowBottom + keyboardHeight - displaySize.y);
+ activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
+ return view.onApplyWindowInsets(insets);
+ });
+
}
return mActivityView;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 429bce0..5cfc20b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -23,7 +23,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -139,6 +138,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -4946,4 +4946,19 @@
portalWindowHandle.portalToDisplayId = mDisplayId;
return portalWindowHandle;
}
+
+ /**
+ * @see IWindowManager#setForwardedInsets
+ */
+ public void setForwardedInsets(Insets insets) {
+ if (insets == null) {
+ insets = Insets.NONE;
+ }
+ if (mDisplayPolicy.getForwardedInsets().equals(insets)) {
+ return;
+ }
+ mDisplayPolicy.setForwardedInsets(insets);
+ setLayoutNeeded();
+ mWmService.mWindowPlacerLocked.requestTraversal();
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f98ca3d..2ee30ac 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -104,6 +104,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.localLOGV;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -111,6 +112,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.hardware.power.V1_0.PowerHint;
@@ -341,6 +343,16 @@
private InputConsumer mInputConsumer = null;
+ /**
+ * The area covered by system windows which belong to another display. Forwarded insets is set
+ * in case this is a virtual display, this is displayed on another display that has insets, and
+ * the bounds of this display is overlapping with the insets of the host display (e.g. IME is
+ * displayed on the host display, and it covers a part of this virtual display.)
+ * The forwarded insets is used to compute display frames of this virtual display, which will
+ * be then used to layout windows in the virtual display.
+ */
+ @NonNull private Insets mForwardedInsets = Insets.NONE;
+
// -------- PolicyHandler --------
private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 1;
private static final int MSG_REQUEST_TRANSIENT_BARS = 2;
@@ -1364,6 +1376,15 @@
displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
displayFrames.mStable.top);
}
+
+ // In case this is a virtual display, and the host display has insets that overlap this
+ // virtual display, apply the insets of the overlapped area onto the current and content
+ // frame of this virtual display. This let us layout windows in the virtual display as
+ // expected when the window needs to avoid overlap with the system windows.
+ // TODO: Generalize the forwarded insets, so that we can handle system windows other than
+ // IME.
+ displayFrames.mCurrent.inset(mForwardedInsets);
+ displayFrames.mContent.inset(mForwardedInsets);
}
private void layoutScreenDecorWindows(DisplayFrames displayFrames) {
@@ -2727,6 +2748,18 @@
}
}
+ /**
+ * @see IWindowManager#setForwardedInsets
+ */
+ public void setForwardedInsets(@NonNull Insets forwardedInsets) {
+ mForwardedInsets = forwardedInsets;
+ }
+
+ @NonNull
+ public Insets getForwardedInsets() {
+ return mForwardedInsets;
+ }
+
@NavigationBarPosition
int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
if (navigationBarCanMove() && displayWidth > displayHeight) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3bb6608..6c3e1f4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -134,6 +134,7 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Bitmap;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -6438,6 +6439,23 @@
}
}
+ @Override
+ public void setForwardedInsets(int displayId, Insets insets) throws RemoteException {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ return;
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int displayOwnerUid = dc.getDisplay().getOwnerUid();
+ if (callingUid != displayOwnerUid) {
+ throw new SecurityException(
+ "Only owner of the display can set ForwardedInsets to it.");
+ }
+ dc.setForwardedInsets(insets);
+ }
+ }
+
void intersectDisplayInsetBounds(Rect display, Rect insets, Rect inOutBounds) {
mTmpRect3.set(display);
mTmpRect3.inset(insets);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 845a09f..4279c41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -28,6 +28,8 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -37,6 +39,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -350,6 +353,48 @@
}
@Test
+ public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
+ synchronized (mWm.mGlobalLock) {
+ mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
+ addWindow(mWindow);
+
+ final int forwardedInsetBottom = 50;
+ mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(),
+ STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(),
+ STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
+ assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
+ assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+ }
+ }
+
+ @Test
+ public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
+ synchronized (mWm.mGlobalLock) {
+ mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
+ addWindow(mWindow);
+
+ final int forwardedInsetBottom = 50;
+ mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
+ assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+ }
+ }
+
+ @Test
public void layoutHint_appWindow() {
synchronized (mWm.mGlobalLock) {
// Initialize DisplayFrames