diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7a460ee10c506..560c79d7565a7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -816,6 +816,7 @@ package android.content.pm { 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_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L + field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 } @@ -3148,7 +3149,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 68b063130128b..fdfa1b2988e35 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1137,6 +1137,34 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // buganizer id + /** + * This change id forces the packages it is applied to sandbox {@link android.view.View} API to + * an activity bounds for: + * + *

{@link android.view.View#getLocationOnScreen}, + * {@link android.view.View#getWindowVisibleDisplayFrame}, + * {@link android.view.View}#getWindowDisplayFrame, + * {@link android.view.View}#getBoundsOnScreen. + * + *

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. + * + *

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: diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a5693f3b447b5..06dab15c70ee1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8919,7 +8919,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; } @@ -8927,6 +8928,7 @@ 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)); + mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect); } /** @@ -15964,7 +15966,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; @@ -26173,7 +26176,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, getLocationInWindow(outLocation); final AttachInfo info = mAttachInfo; - if (info != null) { + + // Need to offset the outLocation with the window bounds, but only if "Sandboxing View + // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation} + // within activity bounds. + if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) { outLocation[0] += info.mWindowLeft; outLocation[1] += info.mWindowTop; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index de946a4572890..38318b604d14f 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; @@ -79,6 +80,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; @@ -98,6 +100,7 @@ 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; @@ -888,6 +891,15 @@ public final class ViewRootImpl implements ViewParent, private boolean mRelayoutRequested; + /** + * Whether sandboxing of {@link android.view.View#getBoundsOnScreen}, + * {@link android.view.View#getLocationOnScreen}, + * {@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; private AccessibilityWindowAttributes mAccessibilityWindowAttributes; @@ -978,6 +990,8 @@ public final class ViewRootImpl implements ViewParent, mViewConfiguration, mContext.getSystemService(InputMethodManager.class)); + mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled(); + String processorOverrideName = context.getResources().getString( R.string.config_inputEventCompatProcessorOverrideClassName); if (processorOverrideName.isEmpty()) { @@ -8544,6 +8558,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 + // {@link #mTmpFrames.displayFrame} is received from the server. + applyViewBoundsSandboxingIfNeeded(outFrame); } /** @@ -8560,6 +8577,60 @@ 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 + // {@link #mTmpFrames.displayFrame} is received from the server. + applyViewBoundsSandboxingIfNeeded(outFrame); + } + + /** + * Offset outRect to make it sandboxed within Window's bounds. + * + *

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 (isViewBoundsSandboxingEnabled()) { + inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop); + } + } + + /** + * Whether the sanboxing of the {@link android.view.View} APIs is enabled. + * + *

This is called by {@link #applyViewBoundsSandboxingIfNeeded} and + * {@link android.view.View#getLocationOnScreen} to check if there is a need to add + * {@link android.view.View.AttachInfo.mWindowLeft} and + * {@link android.view.View.AttachInfo.mWindowTop} offsets. + */ + boolean isViewBoundsSandboxingEnabled() { + return mViewBoundsSandboxingEnabled; + } + + private boolean getViewBoundsSandboxingEnabled() { + if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) { + // 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 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 145a5d3906f19..367fd038dc0fe 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -853,6 +853,42 @@ public interface WindowManager extends ViewManager { String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; + /** + * 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. + * + *

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. + * + *

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. + * + *

Not setting this property at all, or setting this property to {@code true} has no effect. + * + *

Syntax: + *

+     * <application>
+     *   <property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+     *     android:value="false"/>
+     * </application>
+     * 
+ * + * @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 app should be excluded from the