summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Oleg Blinnikov <olb@google.com> 2023-02-12 10:14:24 +0000
committer Oleg Blinnikov <olb@google.com> 2023-02-24 21:11:09 +0000
commitd00518648ccffb1f6ac60cd0c82f7ebd75cb5fe5 (patch)
tree12a12e3f509d6faca44fc3e99f3f4d2b531dd59c
parenta100480ad7c17db91f7562a330860a3a378858be (diff)
Per-app override sandboxing View API to Activity bounds
Some applications assume that they occupy the whole screen and therefore use the display coordinates in their calculations. This can lead to shifted or out of bounds UI elements in case the activity is Letterboxed or is in split-screen mode. OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change id forces the packages it is applied to sandbox View API to Activity bounds for: android.view.View#getBoundsOnScreen android.view.View#getLocationOnScreen android.view.View#getWindowVisibleDisplayFrame android.view.View#getWindowDisplayFrame This sandboxing is happening indirectly in android.view.ViewRootImpl through android.view.ViewRootImpl#getWindowVisibleDisplayFrame, android.view.ViewRootImpl#getDisplayFrame respectively. Application developers can opt-out of this treatment by using the following configuration in their manifest: <application> <property android:name= "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS" android:value="false"/> </application> The difference between this CL and the previous attempt in commit:Ia064d26b46402a04056f498a1ed5c090a6d1b965 is that in the current CL the window bounds are found with windowConfiguration.getBounds(), which is subtracted from displayFrame to make the latter appear as occupying the whole screen without letterbox or multi-window mode. getBounds also takes compatScale and overrideScale into account so no additional scaling is needed. This compat change is taking effect only in non-system processes, this important because all compat changes are enabled for the system process. For example this compat change may impact ANR dialog created within the system process, but it does not require this compat change to be enabled. Test: atest CtsWindowManagerDeviceTestCases:CompatChangeTests Bug: 234799838 Change-Id: If69772bea750380e9f127cea31141a5f953c4e30 Merged-In: If69772bea750380e9f127cea31141a5f953c4e30
-rw-r--r--core/api/test-current.txt3
-rw-r--r--core/java/android/content/pm/ActivityInfo.java28
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/android/view/ViewRootImpl.java81
-rw-r--r--core/java/android/view/WindowManager.java36
5 files changed, 158 insertions, 2 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c0e89d2c4a05..14bf5320a438 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,6 +797,7 @@ package android.content.pm {
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -2917,7 +2918,9 @@ package android.view {
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index bbe99f59fc21..226278cbe44d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1104,6 +1104,34 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d7480e5037f4..543a22b0f4d0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8774,7 +8774,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8782,6 +8783,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15586,7 +15590,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -25786,6 +25791,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 953f17a3a827..b992f7c131d7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -82,6 +83,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -94,12 +96,14 @@ import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -871,6 +875,15 @@ public final class ViewRootImpl implements ViewParent,
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
/**
@@ -958,6 +971,8 @@ public final class ViewRootImpl implements ViewParent,
mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8426,6 +8441,9 @@ public final class ViewRootImpl implements ViewParent,
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8442,6 +8460,69 @@ public final class ViewRootImpl implements ViewParent,
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 17df585e424a..a01c8328b37c 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -854,6 +854,42 @@ public interface WindowManager extends ViewManager {
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get