Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39aa417c authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

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>

Test: atest CtsWindowManagerDeviceTestCases:CompatChangeTests
Bug: 234799838
Change-Id: Ia064d26b46402a04056f498a1ed5c090a6d1b965
parent f37cb530
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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);
+28 −0
Original line number Diff line number Diff line
@@ -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:
     *
     * <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:
+10 −3
Original line number Diff line number Diff line
@@ -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;
        }
+71 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <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 (isViewBoundsSandboxingEnabled()) {
            inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
        }
    }

    /**
     * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
     *
     * <p>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<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;
    }

    /**
+36 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <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 app should be excluded from the