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

Commit d0051864 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>

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
parent a100480a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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);
+28 −0
Original line number Diff line number Diff line
@@ -1103,6 +1103,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 −2
Original line number Diff line number Diff line
@@ -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);
        }
    }
+81 −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;
@@ -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;
    }

    /**
+36 −0
Original line number Diff line number Diff line
@@ -852,6 +852,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 application can be opted-in or opted-out