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

Commit a75783be authored by Mariia Sandrikova's avatar Mariia Sandrikova
Browse files

[1/n] Letterbox Reachability: Repositioning on double tap.

This is the first CL for ensuring reachability of letterboxed portrait-only apps in fullscreen landscape device orientation.

The next CLs will add animation, restrict aspect ratio to split screen window size and add educational hints for the gesture.

Current CL allows to customize left and right positions of the letterboxed windows and initially opens the app on the right side of the screen.

Bug: 197549949
Test: atest WmTests:SizeCompatTests
manual with adb shell cmd window set-letterbox-style --isReachabilityEnabled true

Change-Id: I5a4f65510019c0dfc2b7d61efe0847d943467443
parent 4fba1c44
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -4873,6 +4873,21 @@
        or > 1, it is ignored and central positionis used (0.5). -->
    <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>

    <!-- Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
        device orientation. -->
    <bool name="config_letterboxIsReachabilityEnabled">false</bool>

    <!-- Default horizonal position of a center of the letterboxed app window when reachability is
        enabled and an app is fullscreen in landscape device orientation.
        0 corresponds to the left side of the screen and 1 to the right side. If given value < 0.0
        or > 1, it is ignored and right positionis used (1.0). The position multiplier is changed
        to a symmetrical value computed as (1 - current multiplier) after each double tap in the
        letterbox area. -->
    <item name="config_letterboxDefaultPositionMultiplierForReachability"
          format="float" type="dimen">
        0.9
    </item>

    <!-- If true, hide the display cutout with display area -->
    <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>

+2 −0
Original line number Diff line number Diff line
@@ -4253,6 +4253,8 @@
  <java-symbol type="integer" name="config_letterboxBackgroundType" />
  <java-symbol type="color" name="config_letterboxBackgroundColor" />
  <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
  <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
  <java-symbol type="dimen" name="config_letterboxDefaultPositionMultiplierForReachability" />

  <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />

+11 −5
Original line number Diff line number Diff line
@@ -7534,11 +7534,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                    parentAppBounds.width(), screenResolvedBounds.width());
        } else {
            float positionMultiplier =
                    mWmService.mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
            positionMultiplier =
                    (positionMultiplier < 0.0f || positionMultiplier > 1.0f)
                            // Default to central position if invalid value is provided.
                            ? 0.5f : positionMultiplier;
                    mLetterboxUiController.getHorizontalPositionMultiplier(newParentConfiguration);
            offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
                    * positionMultiplier);
        }
@@ -7555,6 +7551,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
    }

    void recomputeConfiguration() {
        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
    }

    boolean isInTransition() {
        return mAtmService.getTransitionController().inTransition() // Shell transitions.
                || isAnimating(PARENTS | TRANSITION); // Legacy transitions.
    }

    /**
     * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
     * orientation then aspect ratio restrictions are also already respected.
@@ -7657,6 +7662,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // If the activity requires a different orientation (either by override or activityInfo),
        // make it fit the available bounds by scaling down its bounds.
        final int forcedOrientation = getRequestedConfigurationOrientation();

        if (forcedOrientation == ORIENTATION_UNDEFINED
                || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
            return;
+46 −14
Original line number Diff line number Diff line
@@ -19,14 +19,18 @@ package com.android.server.wm;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.SurfaceControl.HIDDEN;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Process;
import android.view.GestureDetector;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.WindowManager;

@@ -65,6 +69,8 @@ public class Letterbox {
    // for overlaping an app window and letterbox surfaces.
    private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
    private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
    // Reachability gestures.
    private final Runnable mDoubleTapCallback;

    /**
     * Constructs a Letterbox.
@@ -77,7 +83,8 @@ public class Letterbox {
            Supplier<Color> colorSupplier,
            Supplier<Boolean> hasWallpaperBackgroundSupplier,
            Supplier<Integer> blurRadiusSupplier,
            Supplier<Float> darkScrimAlphaSupplier) {
            Supplier<Float> darkScrimAlphaSupplier,
            Runnable doubleTapCallback) {
        mSurfaceControlFactory = surfaceControlFactory;
        mTransactionFactory = transactionFactory;
        mAreCornersRounded = areCornersRounded;
@@ -85,6 +92,7 @@ public class Letterbox {
        mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
        mBlurRadiusSupplier = blurRadiusSupplier;
        mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
        mDoubleTapCallback = doubleTapCallback;
    }

    /**
@@ -231,18 +239,48 @@ public class Letterbox {
        return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
    }

    private static class InputInterceptor {
        final InputChannel mClientChannel;
        final InputWindowHandle mWindowHandle;
        final InputEventReceiver mInputEventReceiver;
        final WindowManagerService mWmService;
        final IBinder mToken;
    private final class TapEventReceiver extends InputEventReceiver {

        private final GestureDetector mDoubleTapDetector;
        private final DoubleTapListener mDoubleTapListener;

        TapEventReceiver(InputChannel inputChannel, Context context) {
            super(inputChannel, UiThread.getHandler().getLooper());
            mDoubleTapListener = new DoubleTapListener();
            mDoubleTapDetector = new GestureDetector(context, mDoubleTapListener);
        }

        @Override
        public void onInputEvent(InputEvent event) {
            final MotionEvent motionEvent = (MotionEvent) event;
            finishInputEvent(event, mDoubleTapDetector.onTouchEvent(motionEvent));
        }
    }

    private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            if (e.getAction() == MotionEvent.ACTION_UP) {
                mDoubleTapCallback.run();
                return true;
            }
            return false;
        }
    }

    private final class InputInterceptor {

        private final InputChannel mClientChannel;
        private final InputWindowHandle mWindowHandle;
        private final InputEventReceiver mInputEventReceiver;
        private final WindowManagerService mWmService;
        private final IBinder mToken;

        InputInterceptor(String namePrefix, WindowState win) {
            mWmService = win.mWmService;
            final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
            mClientChannel = mWmService.mInputManager.createInputChannel(name);
            mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);

            mToken = mClientChannel.getToken();

@@ -280,12 +318,6 @@ public class Letterbox {
            mInputEventReceiver.dispose();
            mClientChannel.dispose();
        }

        private static class SimpleInputReceiver extends InputEventReceiver {
            SimpleInputReceiver(InputChannel inputChannel) {
                super(inputChannel, UiThread.getHandler().getLooper());
            }
        }
    }

    private class LetterboxSurface {
+109 −4
Original line number Diff line number Diff line
@@ -85,6 +85,26 @@ final class LetterboxConfiguration {
    // side of the screen and 1.0 to the right side.
    private float mLetterboxHorizontalPositionMultiplier;

    // Default horizontal position of a center of the letterboxed app window when reachability is
    // enabled and an app is fullscreen in landscape device orientatio. 0 corresponds to the left
    // side of the screen and 1.0 to the right side.
    // It is used as a starting point for mLetterboxHorizontalMultiplierForReachability.
    private float mDefaultPositionMultiplierForReachability;

    // Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
    // device orientation.
    private boolean mIsReachabilityEnabled;

    // Horizontal position of a center of the letterboxed app window. 0 corresponds to
    // the left side of the screen and 1 to the right side. Keep it global to prevent
    // "jumps" when switching between letterboxed apps. It's updated to reposition the app
    // window in response to a double tap gesture (see LetterboxUiController#handleDoubleTap).
    // Used in LetterboxUiController#getHorizontalPositionMultiplier which is called from
    // ActivityRecord#updateResolvedBoundsHorizontalPosition.
    // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
    // Overview after changing position in another app.
    private volatile float mLetterboxHorizontalMultiplierForReachability;

    LetterboxConfiguration(Context systemUiContext) {
        mContext = systemUiContext;
        mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -98,6 +118,11 @@ final class LetterboxConfiguration {
                R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
        mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
                R.dimen.config_letterboxHorizontalPositionMultiplier);
        mIsReachabilityEnabled = mContext.getResources().getBoolean(
                R.bool.config_letterboxIsReachabilityEnabled);
        mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
                R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
        mLetterboxHorizontalMultiplierForReachability = mDefaultPositionMultiplierForReachability;
    }

    /**
@@ -317,12 +342,12 @@ final class LetterboxConfiguration {
     * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}
     * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
     * right side.
     *
     * <p>This value can be outside of [0, 1] range so clients need to check and default to the
     * central position (0.5).
     */
    float getLetterboxHorizontalPositionMultiplier() {
        return mLetterboxHorizontalPositionMultiplier;
        return (mLetterboxHorizontalPositionMultiplier < 0.0f
                || mLetterboxHorizontalPositionMultiplier > 1.0f)
                        // Default to central position if invalid value is provided.
                        ? 0.5f : mLetterboxHorizontalPositionMultiplier;
    }

    /**
@@ -344,4 +369,84 @@ final class LetterboxConfiguration {
                com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
    }

    /*
     * Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
     * device orientation.
     */
    boolean getIsReachabilityEnabled() {
        return mIsReachabilityEnabled;
    }

    /**
     * Overrides whether reachability repositioning is allowed for letterboxed fullscreen apps in
     * landscape device orientation.
     */
    void setIsReachabilityEnabled(boolean enabled) {
        mIsReachabilityEnabled = enabled;
    }

    /**
     * Resets whether reachability repositioning is allowed for letterboxed fullscreen apps in
     * landscape device orientation to {@link R.bool.config_letterboxIsReachabilityEnabled}.
     */
    void resetIsReachabilityEnabled() {
        mIsReachabilityEnabled = mContext.getResources().getBoolean(
                R.bool.config_letterboxIsReachabilityEnabled);
    }

    /*
     * Gets default horizontal position of a center of the letterboxed app window when reachability
     * is enabled specified in {@link
     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} or via an ADB command.
     * 0 corresponds to the left side of the screen and 1 to the right side. The returned value is
     * >= 0.0 and <= 1.0.
     */
    float getDefaultPositionMultiplierForReachability() {
        return (mDefaultPositionMultiplierForReachability < 0.0f
                || mDefaultPositionMultiplierForReachability > 1.0f)
                        // Default to a right position if invalid value is provided.
                        ? 1.0f : mDefaultPositionMultiplierForReachability;
    }

    /**
     * Overrides default horizontal position of a center of the letterboxed app window when
     * reachability is enabled. If given value < 0.0 or > 1.0, then it and a value of {@link
     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} are ignored and the right
     * position (1.0) is used.
     */
    void setDefaultPositionMultiplierForReachability(float multiplier) {
        mDefaultPositionMultiplierForReachability = multiplier;
    }

    /**
     * Resets default horizontal position of a center of the letterboxed app window when
     * reachability is enabled to {@link
     * R.dimen.config_letterboxDefaultPositionMultiplierForReachability}.
     */
    void resetDefaultPositionMultiplierForReachability() {
        mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
                R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
    }

    /*
     * Gets horizontal position of a center of the letterboxed app window when reachability
     * is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side.
     *
     * <p>The position multiplier is changed to a symmetrical value computed as (1 - current
     * multiplier) after each double tap in the letterbox area.
     */
    float getHorizontalMultiplierForReachability() {
        return mLetterboxHorizontalMultiplierForReachability;
    }

    /**
     * Changes horizontal position of a center of the letterboxed app window to the opposite
     * (1 - current multiplier) when reachability is enabled specified. 0 corresponds to the left
     * side of the screen and 1 to the right side.
     */
    void flipHorizontalMultiplierForReachability() {
        mLetterboxHorizontalMultiplierForReachability =
                1.0f - mLetterboxHorizontalMultiplierForReachability;
    }

}
Loading