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

Commit 21c8595d authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Revert "Improve AnchoredWindow with smart positioning logic.""

parents c71593e3 7b25218f
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -520,7 +520,6 @@
    <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>

    <!-- The elevation of AutoFill fill window-->
    <dimen name="autofill_fill_elevation">4dp</dimen>
    <dimen name="autofill_fill_elevation">2dp</dimen>
    <dimen name="autofill_fill_item_height">64dp</dimen>
    <dimen name="autofill_fill_min_margin">16dp</dimen>
</resources>
+0 −1
Original line number Diff line number Diff line
@@ -2834,7 +2834,6 @@
  <!-- com.android.server.autofill -->
  <java-symbol type="dimen" name="autofill_fill_elevation" />
  <java-symbol type="dimen" name="autofill_fill_item_height" />
  <java-symbol type="dimen" name="autofill_fill_min_margin" />
  <java-symbol type="layout" name="autofill_save"/>
  <java-symbol type="id" name="autofill_save_title" />
  <java-symbol type="id" name="autofill_save_no" />
+74 −217
Original line number Diff line number Diff line
@@ -17,277 +17,134 @@ package com.android.server.autofill;

import static com.android.server.autofill.Helper.DEBUG;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;

import java.io.PrintWriter;
/**
 * A window above the application that is smartly anchored to a rectangular region.
 */
final class AnchoredWindow implements View.OnLayoutChangeListener, View.OnTouchListener {
final class AnchoredWindow {
    private static final String TAG = "AutoFill";

    private static final int NULL_HEIGHT = -1;

    private final WindowManager mWm;
    private final IBinder mAppToken;
    private final View mContentView;

    private final View mWindowSizeListenerView;
    private final int mMinMargin;

    private int mLastHeight = NULL_HEIGHT;
    @Nullable
    private Rect mLastBounds;
    @Nullable
    private Rect mLastDisplayBounds;
    private final View mRootView;
    private final View mView;
    private final int mWidth;
    private final int mHeight;
    private boolean mIsShowing = false;

    /**
     * Constructor.
     *
     * @param wm window manager that draws the content on a window
     * @param appToken token to pass to window manager
     * @param contentView content of the window
     * @param wm window manager that draws the view on a window
     * @param view singleton view in the window
     * @param width requested width of the view
     * @param height requested height of the view
     */
    AnchoredWindow(WindowManager wm, IBinder appToken, View contentView) {
    AnchoredWindow(WindowManager wm, View view, int width, int height) {
        mWm = wm;
        mAppToken = appToken;
        mContentView = contentView;

        mContentView.addOnLayoutChangeListener(this);

        Context context = contentView.getContext();

        mWindowSizeListenerView = new FrameLayout(context);
        mWindowSizeListenerView.addOnLayoutChangeListener(this);

        mMinMargin = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.autofill_fill_min_margin);
        mRootView = wrapView(view, width, height);
        mView = view;
        mWidth = width;
        mHeight = height;
    }

    /**
     * Shows the window.
     *
     * @param bounds the region the window should be anchored to
     * @param bounds the rectangular region this window should be anchored to
     */
    void show(Rect bounds) {
        if (DEBUG) Slog.d(TAG, "show bounds=" + bounds);
        final LayoutParams params = createBaseLayoutParams();
        params.x = bounds.left;
        params.y = bounds.bottom;

        if (!mWindowSizeListenerView.isAttachedToWindow()) {
            if (DEBUG) Slog.d(TAG, "adding mWindowSizeListenerView");
            LayoutParams params = createWindowLayoutParams(
                    mAppToken,
                    LayoutParams.FLAG_NOT_TOUCHABLE); // not touchable
            params.gravity = Gravity.LEFT | Gravity.TOP;
            params.x = 0;
            params.y = 0;
            params.width = LayoutParams.MATCH_PARENT;
            params.height = LayoutParams.MATCH_PARENT;
            mWm.addView(mWindowSizeListenerView, params);
        if (!mIsShowing) {
            if (DEBUG) Slog.d(TAG, "adding view " + mView);
            mWm.addView(mRootView, params);
        } else {
            if (DEBUG) Slog.d(TAG, "updating view " + mView);
            mWm.updateViewLayout(mRootView, params);
        }

        updateBounds(bounds);
        mIsShowing = true;
    }

    /**
     * Hides the window.
     */
    void hide() {
        if (DEBUG) Slog.d(TAG, "hide");

        mLastHeight = NULL_HEIGHT;
        mLastBounds = null;
        mLastDisplayBounds = null;

        if (mWindowSizeListenerView.isAttachedToWindow()) {
            if (DEBUG) Slog.d(TAG, "removing mWindowSizeListenerView");
            mWm.removeView(mWindowSizeListenerView);
        }

        if (mContentView.isAttachedToWindow()) {
            if (DEBUG) Slog.d(TAG, "removing mContentView");
            mContentView.setOnTouchListener(null);
            mWm.removeView(mContentView);
        }
    }
        if (DEBUG) Slog.d(TAG, "removing view " + mView);

    @Override
    public void onLayoutChange(View view, int left, int top, int right, int bottom,
            int oldLeft, int oldTop, int oldRight, int oldBottom) {
        if (view == mWindowSizeListenerView) {
            if (DEBUG) Slog.d(TAG, "onLayoutChange() for mWindowSizeListenerView");
            // mWindowSizeListenerView layout changed, get the size of the display bounds and update
            // the window.
            final Rect displayBounds = new Rect();
            view.getBoundsOnScreen(displayBounds);
            updateDisplayBounds(displayBounds);
        } else if (view == mContentView) {
            // mContentView layout changed, update the window in case its height changed.
            if (DEBUG) Slog.d(TAG, "onLayoutChange() for mContentView");
            updateHeight();
        }
    }

    // When the window is touched outside, hide the window.
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (view == mContentView && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
            hide();
            return true;
        if (mIsShowing) {
            mWm.removeView(mRootView);
        }
        return false;
        mIsShowing = false;
    }

    private boolean updateHeight() {
        final Rect displayBounds = mLastDisplayBounds;
        if (displayBounds == null) {
            return false;
        }

        mContentView.measure(
                MeasureSpec.makeMeasureSpec(displayBounds.width(), MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(displayBounds.height(), MeasureSpec.AT_MOST));
        int height = mContentView.getMeasuredHeight();
        if (height != mLastHeight) {
            if (DEBUG) Slog.d(TAG, "update height=" + height);
            mLastHeight = height;
            update(height, mLastBounds, displayBounds);
            return true;
        } else {
            return false;
        }
    /**
     * Wraps a view with a SelfRemovingView and sets its requested width and height.
     */
    private View wrapView(View view, int width, int height) {
        final ViewGroup viewGroup = new SelfRemovingView(view.getContext());
        viewGroup.addView(view, new ViewGroup.LayoutParams(width, height));
        return viewGroup;
    }

    private void updateBounds(Rect bounds) {
        if (!bounds.equals(mLastBounds)) {
            if (DEBUG) Slog.d(TAG, "update bounds=" + bounds);
            mLastBounds = bounds;

            update(mLastHeight, bounds, mLastDisplayBounds);
        }
    private static LayoutParams createBaseLayoutParams() {
        final LayoutParams params = new LayoutParams();
        // TODO(b/33197203): LayoutParams.TYPE_AUTOFILL
        params.type = LayoutParams.TYPE_SYSTEM_ALERT;
        params.flags =
                LayoutParams.SOFT_INPUT_STATE_UNCHANGED
                | LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | LayoutParams.FLAG_LAYOUT_NO_LIMITS
                | LayoutParams.FLAG_NOT_FOCUSABLE
                | LayoutParams.FLAG_NOT_TOUCH_MODAL
                | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.width = LayoutParams.WRAP_CONTENT;
        params.height = LayoutParams.WRAP_CONTENT;
        return params;
    }

    private void updateDisplayBounds(Rect displayBounds) {
        if (!displayBounds.equals(mLastDisplayBounds)) {
            if (DEBUG) Slog.d(TAG, "update displayBounds=" + displayBounds);
            mLastDisplayBounds = displayBounds;

            if (!updateHeight()) {
                update(mLastHeight, mLastBounds, displayBounds);
            }
        }
    }
    @Override
    public String toString() {
        if (!DEBUG) return super.toString();

    // Updates the window if height, bounds, and displayBounds are not null.
    // Caller should ensure that something changed before calling.
    private void update(int height, @Nullable Rect bounds, @Nullable Rect displayBounds) {
        if (height == NULL_HEIGHT || bounds == null || displayBounds == null) {
            return;
        return "AnchoredWindow: [width=" + mWidth + ", height=" + mHeight + ", view=" + mView + "]";
    }

        if (DEBUG) Slog.d(TAG, "update height=" + height + ", bounds=" + bounds
                + ", displayBounds=" + displayBounds);

        final LayoutParams params = createWindowLayoutParams(mAppToken,
                LayoutParams.FLAG_NOT_TOUCH_MODAL // outside touches go to windows behind us
                | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); // outside touches trigger MotionEvent
        params.setTitle("AutoFill Fill"); // used for debugging
        updatePosition(params, height, mMinMargin, bounds, displayBounds);
        if (!mContentView.isAttachedToWindow()) {
            if (DEBUG) Slog.d(TAG, "adding mContentView");
            mWm.addView(mContentView, params);
            mContentView.setOnTouchListener(this);
        } else {
            if (DEBUG) Slog.d(TAG, "updating mContentView");
            mWm.updateViewLayout(mContentView, params);
        }
    void dump(PrintWriter pw) {
        pw.println("Anchored Window");
        final String prefix = "  ";
        pw.print(prefix); pw.print("width: "); pw.println(mWidth);
        pw.print(prefix); pw.print("height: "); pw.println(mHeight);
        pw.print(prefix); pw.print("visible: "); pw.println(mIsShowing);
    }

    /**
     * Updates the position of the window by altering the {@link LayoutParams}.
     *
     * <p>The window can be anchored either above or below the bounds. Anchoring the window below
     * the bounds is preferred, if it fits. Otherwise, anchor the window on the side with more
     * space.
     *
     * @param params the params to update
     * @param height the requested height of the window
     * @param minMargin the minimum margin between the window and the display bounds
     * @param bounds the region the window should be anchored to
     * @param displayBounds the region in which the window may be displayed
     */
    private static void updatePosition(
            LayoutParams params,
            int height,
            int minMargin,
            Rect bounds,
            Rect displayBounds) {
        boolean below;
        int verticalSpace;
        final int verticalSpaceBelow = displayBounds.bottom - bounds.bottom - minMargin;
        if (height <= verticalSpaceBelow) {
            // Fits below bounds.
            below = true;
            verticalSpace = height;
        } else {
            final int verticalSpaceAbove = bounds.top - displayBounds.top - minMargin;
            if (height <= verticalSpaceAbove) {
                // Fits above bounds.
                below = false;
                verticalSpace = height;
            } else {
                // Pick above/below based on which has the most space.
                if (verticalSpaceBelow >= verticalSpaceAbove) {
                    below = true;
                    verticalSpace = verticalSpaceBelow;
                } else {
                    below = false;
                    verticalSpace = verticalSpaceAbove;
                }
            }
    /** FrameLayout that listens for touch events removes itself if the touch event is outside. */
    private final class SelfRemovingView extends FrameLayout {
        public SelfRemovingView(Context context) {
            super(context);
        }

        int gravity;
        int y;
        if (below) {
            if (DEBUG) Slog.d(TAG, "anchorBelow");
            gravity = Gravity.TOP | Gravity.LEFT;
            y = bounds.bottom - displayBounds.top;
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                hide();
                return true;
            } else {
            if (DEBUG) Slog.d(TAG, "anchorAbove");
            gravity = Gravity.BOTTOM | Gravity.LEFT;
            y = displayBounds.bottom - bounds.top;
                return super.onTouchEvent(event);
            }

        final int x = bounds.left - displayBounds.left;

        params.gravity = gravity;
        params.x = x;
        params.y = y;
        params.width = bounds.width();
        params.height = verticalSpace;
        }

    private static LayoutParams createWindowLayoutParams(IBinder appToken, int flags) {
        final LayoutParams params = new LayoutParams();
        params.token = appToken;
        params.type = LayoutParams.TYPE_PHONE;
        params.flags =
                flags
                | LayoutParams.FLAG_NOT_FOCUSABLE // don't receive input events
                | LayoutParams.FLAG_ALT_FOCUSABLE_IM; // resize for soft input
        params.format = PixelFormat.TRANSLUCENT;
        return params;
    }
}
+20 −5
Original line number Diff line number Diff line
@@ -69,6 +69,8 @@ final class AutoFillUI {
    private AnchoredWindow mFillWindow;
    private DatasetPicker mFillView;
    private ViewState mViewState;
    private Rect mBounds;
    private String mFilterText;

    /**
     * Custom snackbar UI used for saving autofill or other informational messages.
@@ -112,6 +114,8 @@ final class AutoFillUI {
        }

        mViewState = null;
        mBounds = null;
        mFilterText = null;
        mFillView = null;
        mFillWindow = null;
    }
@@ -139,14 +143,23 @@ final class AutoFillUI {
                            mSession.autoFillApp(dataset);
                            hideFillUi();
                        });
                mFillWindow = new AnchoredWindow(mWm, mAppToken, mFillView);
                mFillWindow = new AnchoredWindow(
                        mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);

                if (DEBUG) Slog.d(TAG, "showFillUi(): view changed");
                if (DEBUG) Slog.d(TAG, "show FillUi");
            }

            if (DEBUG) Slog.d(TAG, "showFillUi(): bounds=" + bounds + ", filterText=" + filterText);
            mFillView.update(filterText);
            mFillWindow.show(bounds);
            if (!bounds.equals(mBounds)) {
                if (DEBUG) Slog.d(TAG, "update FillUi bounds: " + mBounds);
                mBounds = bounds;
                mFillWindow.show(mBounds);
            }

            if (!filterText.equals(mFilterText)) {
                if (DEBUG) Slog.d(TAG, "update FillUi filter text: " + mFilterText);
                mFilterText = filterText;
                mFillView.update(mFilterText);
            }
        }, 0);
    }

@@ -235,6 +248,8 @@ final class AutoFillUI {
        pw.print(prefix); pw.print("mSessionId: "); pw.println(mSession.mId);
        pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
        pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
        pw.print(prefix); pw.print("mBounds: "); pw.println(mBounds);
        pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
    }

    //similar to a snackbar, but can be a bit custom since it is more than just text. This will