diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c2cd895b5e3cc..927988f712832 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -520,7 +520,6 @@ 800dp - 4dp + 2dp 64dp - 16dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 227d8b61764bb..571aa17304849 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2833,7 +2833,6 @@ - diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java index e53165d4b567e..c68ac60bb26bb 100644 --- a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java +++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java @@ -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"); + if (DEBUG) Slog.d(TAG, "removing view " + mView); - 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); - } - } - - @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; - } - return 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; - } - } - - private void updateBounds(Rect bounds) { - if (!bounds.equals(mLastBounds)) { - if (DEBUG) Slog.d(TAG, "update bounds=" + bounds); - mLastBounds = bounds; - - update(mLastHeight, bounds, mLastDisplayBounds); - } - } - - 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); - } - } - } - - // 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; - } - - 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); + if (mIsShowing) { + mWm.removeView(mRootView); } + mIsShowing = false; } /** - * Updates the position of the window by altering the {@link LayoutParams}. - * - *

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 + * Wraps a view with a SelfRemovingView and sets its requested width and height. */ - 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; - } - } - } - - int gravity; - int y; - if (below) { - if (DEBUG) Slog.d(TAG, "anchorBelow"); - gravity = Gravity.TOP | Gravity.LEFT; - y = bounds.bottom - displayBounds.top; - } else { - if (DEBUG) Slog.d(TAG, "anchorAbove"); - gravity = Gravity.BOTTOM | Gravity.LEFT; - y = displayBounds.bottom - bounds.top; - } - - final int x = bounds.left - displayBounds.left; - - params.gravity = gravity; - params.x = x; - params.y = y; - params.width = bounds.width(); - params.height = verticalSpace; + 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 static LayoutParams createWindowLayoutParams(IBinder appToken, int flags) { + private static LayoutParams createBaseLayoutParams() { final LayoutParams params = new LayoutParams(); - params.token = appToken; - params.type = LayoutParams.TYPE_PHONE; + // TODO(b/33197203): LayoutParams.TYPE_AUTOFILL + params.type = LayoutParams.TYPE_SYSTEM_ALERT; 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; + 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; } + + @Override + public String toString() { + if (!DEBUG) return super.toString(); + + return "AnchoredWindow: [width=" + mWidth + ", height=" + mHeight + ", view=" + mView + "]"; + } + + 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); + } + + /** 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); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + hide(); + return true; + } else { + return super.onTouchEvent(event); + } + } + } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index 73f488d39f154..7d82bec4996c7 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -71,6 +71,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. @@ -115,6 +117,8 @@ final class AutoFillUI { } mViewState = null; + mBounds = null; + mFilterText = null; mFillView = null; mFillWindow = null; } @@ -142,14 +146,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); } @@ -238,6 +251,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