From 36b86c28f88e4c7853a4255a0fd9b754cbb547c4 Mon Sep 17 00:00:00 2001 From: Dake Gu Date: Mon, 16 Apr 2018 12:49:30 -0700 Subject: [PATCH] Autofill: new UX for TV and support themes 1. Define default Themes for autofill window and save dialog. (http://go/theme_autofill). Phone uses light themes, TV uses dark themes. 2. Apply autofill theme to RemoteViews passed from autofill service. So this can make sure the textColor of RemoteViews matches the background of autofill theme uses. Updated public javadoc that autofill service should not hardcode color values. 3. A new TV ux that occupies half screen height (go/autofill-for-tv). TV autofill now passes unhandled physical keyevent to app window in the same way phone/tablet does. 4. Fixed ATV autofill window to be SYSTEM_DIALOG, so it wont be clipped by app activity window (DialogLauncherActivityTest). Bug: 71720680 Bug: 74072921 Test: CtsAutofillTest Change-Id: Ib570227b0958b1800e8f0600b8aec36478568d74 --- .../service/autofill/BatchUpdates.java | 3 + .../android/service/autofill/Dataset.java | 3 + .../service/autofill/FillResponse.java | 9 + .../view/autofill/AutofillPopupWindow.java | 48 +++- core/java/android/widget/RemoteViews.java | 20 ++ .../res/layout-television/autofill_save.xml | 100 +++++++++ .../res/layout/autofill_dataset_picker.xml | 5 +- .../autofill_dataset_picker_fullscreen.xml | 76 +++++-- .../autofill_dataset_picker_header_footer.xml | 5 +- ...ataset_picker_header_footer_fullscreen.xml | 73 ------ core/res/res/values-television/dimens.xml | 4 - .../themes_device_defaults.xml | 2 + core/res/res/values/colors_material.xml | 4 + core/res/res/values/strings.xml | 4 +- core/res/res/values/styles.xml | 8 +- core/res/res/values/symbols.xml | 9 +- .../res/res/values/themes_device_defaults.xml | 9 + core/res/res/values/themes_material.xml | 21 ++ .../com/android/server/autofill/Session.java | 3 +- .../server/autofill/ui/AutoFillUI.java | 7 +- .../android/server/autofill/ui/FillUi.java | 210 ++++++++---------- .../android/server/autofill/ui/SaveUi.java | 8 +- 22 files changed, 394 insertions(+), 237 deletions(-) create mode 100644 core/res/res/layout-television/autofill_save.xml delete mode 100644 core/res/res/layout/autofill_dataset_picker_header_footer_fullscreen.xml diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java index 90acc881e171..2ba03762ec29 100644 --- a/core/java/android/service/autofill/BatchUpdates.java +++ b/core/java/android/service/autofill/BatchUpdates.java @@ -82,6 +82,9 @@ public final class BatchUpdates implements Parcelable { * {@link #transformChild(int, Transformation) transformations} are applied to the children * views. * + *

Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + * * @param updates a {@link RemoteViews} with the updated actions to be applied in the * underlying presentation template. * diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index ccec483d046b..521176797657 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -336,6 +336,9 @@ public final class Dataset implements Parcelable { * higher, datasets that require authentication can be also be filtered by passing a * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. * + *

Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 2bc4b8f7baf2..7bf1f83f6bd8 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -241,6 +241,9 @@ public final class FillResponse implements Parcelable { * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * + *

Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + * * @param authentication Intent to an activity with your authentication flow. * @param presentation The presentation to visualize the response. * @param ids id of Views that when focused will display the authentication UI. @@ -449,6 +452,9 @@ public final class FillResponse implements Parcelable { * authentication (as the header could have been set directly in the main presentation in * these cases). * + *

Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + * * @param header a presentation to represent the header. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would @@ -477,6 +483,9 @@ public final class FillResponse implements Parcelable { * authentication (as the footer could have been set directly in the main presentation in * these cases). * + *

Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + * * @param footer a presentation to represent the footer. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java index a6495d154941..826620710b18 100644 --- a/core/java/android/view/autofill/AutofillPopupWindow.java +++ b/core/java/android/view/autofill/AutofillPopupWindow.java @@ -19,6 +19,7 @@ package android.view.autofill; import static android.view.autofill.Helper.sVerbose; import android.annotation.NonNull; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.IBinder; @@ -79,11 +80,6 @@ public class AutofillPopupWindow extends PopupWindow { public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { mWindowPresenter = new WindowPresenter(presenter); - // We want to show the window as system controlled one so it covers app windows, but it has - // to be an application type (so it's contained inside the application area). - // Hence, we set it to the application type with the highest z-order, which currently - // is TYPE_APPLICATION_ABOVE_SUB_PANEL. - setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); setTouchModal(false); setOutsideTouchable(true); setInputMethodMode(INPUT_METHOD_NOT_NEEDED); @@ -110,7 +106,16 @@ public class AutofillPopupWindow extends PopupWindow { */ public void update(View anchor, int offsetX, int offsetY, int width, int height, Rect virtualBounds) { - mFullScreen = width == LayoutParams.MATCH_PARENT && height == LayoutParams.MATCH_PARENT; + mFullScreen = width == LayoutParams.MATCH_PARENT; + // For no fullscreen autofill window, we want to show the window as system controlled one + // so it covers app windows, but it has to be an application type (so it's contained inside + // the application area). Hence, we set it to the application type with the highest z-order, + // which currently is TYPE_APPLICATION_ABOVE_SUB_PANEL. + // For fullscreen mode, autofill window is at the bottom of screen, it should not be + // clipped by app activity window. Fullscreen autofill window does not need to follow app + // anchor view position. + setWindowLayoutType(mFullScreen ? WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG + : WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); // If we are showing the popup for a virtual view we use a fake view which // delegates to the anchor but present itself with the same bounds as the // virtual view. This ensures that the location logic in popup works @@ -119,6 +124,15 @@ public class AutofillPopupWindow extends PopupWindow { if (mFullScreen) { offsetX = 0; offsetY = 0; + // If it is not fullscreen height, put window at bottom. Computes absolute position. + // Note that we cannot easily change default gravity from Gravity.TOP to + // Gravity.BOTTOM because PopupWindow base class does not expose computeGravity(). + final Point outPoint = new Point(); + anchor.getContext().getDisplay().getSize(outPoint); + width = outPoint.x; + if (height != LayoutParams.MATCH_PARENT) { + offsetY = outPoint.y - height; + } actualAnchor = anchor; } else if (virtualBounds != null) { final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top}; @@ -202,6 +216,16 @@ public class AutofillPopupWindow extends PopupWindow { actualAnchor = anchor; } + if (!mFullScreen) { + // No fullscreen window animation is controlled by PopupWindow. + setAnimationStyle(-1); + } else if (height == LayoutParams.MATCH_PARENT) { + // Complete fullscreen autofill window has no animation. + setAnimationStyle(0); + } else { + // Slide half screen height autofill window from bottom. + setAnimationStyle(com.android.internal.R.style.AutofillHalfScreenAnimation); + } if (!isShowing()) { setWidth(width); setHeight(height); @@ -223,7 +247,12 @@ public class AutofillPopupWindow extends PopupWindow { protected boolean findDropDownPosition(View anchor, LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { if (mFullScreen) { - // Do not patch LayoutParams if force full screen + // In fullscreen mode, don't need consider the anchor view. + outParams.x = xOffset; + outParams.y = yOffset; + outParams.width = width; + outParams.height = height; + outParams.gravity = gravity; return false; } return super.findDropDownPosition(anchor, outParams, xOffset, yOffset, @@ -315,11 +344,6 @@ public class AutofillPopupWindow extends PopupWindow { throw new IllegalStateException("You can't call this!"); } - @Override - public void setAnimationStyle(int animationStyle) { - throw new IllegalStateException("You can't call this!"); - } - @Override public void setBackgroundDrawable(Drawable background) { throw new IllegalStateException("You can't call this!"); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index b6bd14ed5efd..70c48e5cef88 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.ColorInt; import android.annotation.DimenRes; import android.annotation.NonNull; +import android.annotation.StyleRes; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; @@ -56,6 +57,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; @@ -181,6 +183,12 @@ public class RemoteViews implements Parcelable, Filter { */ private boolean mIsRoot = true; + /** + * Optional theme resource id applied in inflateView(). When 0, Theme.DeviceDefault will be + * used. + */ + private int mApplyThemeResId; + /** * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify * the layout in a way that isn't recoverable, since views are being removed. @@ -3247,6 +3255,14 @@ public class RemoteViews implements Parcelable, Filter { return this; } + /** + * Set the theme used in apply() and applyASync(). + * @hide + */ + public void setApplyTheme(@StyleRes int themeResId) { + mApplyThemeResId = themeResId; + } + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. @@ -3282,6 +3298,10 @@ public class RemoteViews implements Parcelable, Filter { final Context contextForResources = getContextForResources(context); Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); + // If mApplyThemeResId is not given, Theme.DeviceDefault will be used. + if (mApplyThemeResId != 0) { + inflationContext = new ContextThemeWrapper(inflationContext, mApplyThemeResId); + } LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); diff --git a/core/res/res/layout-television/autofill_save.xml b/core/res/res/layout-television/autofill_save.xml new file mode 100644 index 000000000000..ebd2dec3fc0f --- /dev/null +++ b/core/res/res/layout-television/autofill_save.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/layout/autofill_dataset_picker.xml b/core/res/res/layout/autofill_dataset_picker.xml index ef19f870006f..a88836eff5a0 100644 --- a/core/res/res/layout/autofill_dataset_picker.xml +++ b/core/res/res/layout/autofill_dataset_picker.xml @@ -14,8 +14,7 @@ limitations under the License. --> - - + diff --git a/core/res/res/layout/autofill_dataset_picker_fullscreen.xml b/core/res/res/layout/autofill_dataset_picker_fullscreen.xml index 07298c182269..1d2b5e5d6543 100644 --- a/core/res/res/layout/autofill_dataset_picker_fullscreen.xml +++ b/core/res/res/layout/autofill_dataset_picker_fullscreen.xml @@ -14,35 +14,73 @@ limitations under the License. --> - + - + android:layout_weight="1" + android:layout_marginEnd="32dp"> + + + + + + + + + - + + android:id="@+id/autofill_dataset_picker" + android:orientation="vertical"> - + + - + \ No newline at end of file diff --git a/core/res/res/layout/autofill_dataset_picker_header_footer.xml b/core/res/res/layout/autofill_dataset_picker_header_footer.xml index 048494ac8f29..093f035ec1ea 100644 --- a/core/res/res/layout/autofill_dataset_picker_header_footer.xml +++ b/core/res/res/layout/autofill_dataset_picker_header_footer.xml @@ -14,8 +14,7 @@ limitations under the License. --> - - + diff --git a/core/res/res/layout/autofill_dataset_picker_header_footer_fullscreen.xml b/core/res/res/layout/autofill_dataset_picker_header_footer_fullscreen.xml deleted file mode 100644 index 24b14a061d90..000000000000 --- a/core/res/res/layout/autofill_dataset_picker_header_footer_fullscreen.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/res/res/values-television/dimens.xml b/core/res/res/values-television/dimens.xml index aa5625124f29..4c25225dd50c 100644 --- a/core/res/res/values-television/dimens.xml +++ b/core/res/res/values-television/dimens.xml @@ -20,8 +20,4 @@ 0.15 0.3 - - 60% - 70% - diff --git a/core/res/res/values-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml index e01caa31ef4f..e380a7bb88a6 100644 --- a/core/res/res/values-television/themes_device_defaults.xml +++ b/core/res/res/values-television/themes_device_defaults.xml @@ -16,4 +16,6 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index e6b2a3522fd1..c05506066e6a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2079,7 +2079,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mComponentName.getPackageName(), this); + mService.getServicePackageName(), mComponentName.getPackageName(), + mService.getServiceLabel(), mService.getServiceIcon(), this); synchronized (mLock) { if (mUiShownTime == 0) { diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 21a39e483986..ee18dc2e5824 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -163,11 +163,14 @@ public final class AutoFillUI { * @param filterText text of the view to be filled * @param servicePackageName package name of the autofill service filling the activity * @param packageName package name of the activity that is filled + * @param serviceLabel label of autofill service + * @param serviceIcon icon of autofill service * @param callback Identifier for the caller */ public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, - @NonNull String packageName, @NonNull AutoFillUiCallback callback) { + @NonNull String packageName, @NonNull CharSequence serviceLabel, + @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback) { if (sDebug) { final int size = filterText == null ? 0 : filterText.length(); Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars"); @@ -185,7 +188,7 @@ public final class AutoFillUI { } hideAllUiThread(callback); mFillUi = new FillUi(mContext, response, focusedId, - filterText, mOverlayControl, new FillUi.Callback() { + filterText, mOverlayControl, serviceLabel, serviceIcon, new FillUi.Callback() { @Override public void onResponsePicked(FillResponse response) { log.setType(MetricsEvent.TYPE_DETAIL); diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index d29ca051ad94..1aeb3b914813 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -26,6 +26,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.ContextThemeWrapper; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; @@ -53,9 +55,11 @@ import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; import com.android.server.UiThread; @@ -72,30 +76,10 @@ import java.util.stream.Collectors; final class FillUi { private static final String TAG = "FillUi"; + private static final int THEME_ID = com.android.internal.R.style.Theme_DeviceDefault_Autofill; + private static final TypedValue sTempTypedValue = new TypedValue(); - public static final class AutofillFrameLayout extends FrameLayout { - - OnKeyListener mUnhandledListener; - - public AutofillFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AutofillFrameLayout(Context context, AttributeSet attrs, @AttrRes int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean handled = super.dispatchKeyEvent(event); - if (!handled) { - handled = mUnhandledListener.onKey(this, event.getKeyCode(), event); - } - return handled; - } - } - interface Callback { void onResponsePicked(@NonNull FillResponse response); void onDatasetPicked(@NonNull Dataset dataset); @@ -146,51 +130,64 @@ final class FillUi { FillUi(@NonNull Context context, @NonNull FillResponse response, @NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText, - @NonNull OverlayControl overlayControl, @NonNull Callback callback) { - mContext = context; + @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel, + @NonNull Drawable serviceIcon, @NonNull Callback callback) { mCallback = callback; mFullScreen = isFullScreen(context); - - final LayoutInflater inflater = LayoutInflater.from(context); + mContext = new ContextThemeWrapper(context, THEME_ID); + final LayoutInflater inflater = LayoutInflater.from(mContext); final RemoteViews headerPresentation = response.getHeader(); final RemoteViews footerPresentation = response.getFooter(); final ViewGroup decor; - if (headerPresentation != null || footerPresentation != null) { - decor = (ViewGroup) inflater.inflate( - mFullScreen ? R.layout.autofill_dataset_picker_header_footer_fullscreen - : R.layout.autofill_dataset_picker_header_footer, null); + if (mFullScreen) { + decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null); + } else if (headerPresentation != null || footerPresentation != null) { + decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_header_footer, + null); } else { - decor = (ViewGroup) inflater.inflate( - mFullScreen ? R.layout.autofill_dataset_picker_fullscreen - : R.layout.autofill_dataset_picker, null); + decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker, null); + } + final TextView titleView = decor.findViewById(R.id.autofill_dataset_title); + if (titleView != null) { + titleView.setText(mContext.getString(R.string.autofill_window_title, serviceLabel)); + } + final ImageView iconView = decor.findViewById(R.id.autofill_dataset_icon); + if (iconView != null) { + iconView.setImageDrawable(serviceIcon); } - // if autofill ui is not fullscreen, send unhandled keyevent to app window. - if (!mFullScreen) { - if (decor instanceof AutofillFrameLayout) { - ((AutofillFrameLayout) decor).mUnhandledListener = - (View view, int keyCode, KeyEvent event) -> { - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_ESCAPE: - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_DOWN: - return false; - default: - mCallback.dispatchUnhandledKey(event); - return true; - } - }; - } else { - Slog.wtf(TAG, "Unable to send unhandled key"); + // In full screen we only initialize size once assuming screen size never changes + if (mFullScreen) { + final Point outPoint = mTempPoint; + mContext.getDisplay().getSize(outPoint); + // full with of screen and half height of screen + mContentWidth = LayoutParams.MATCH_PARENT; + mContentHeight = outPoint.y / 2; + if (sVerbose) { + Slog.v(TAG, "initialized fillscreen LayoutParams " + + mContentWidth + "," + mContentHeight); } } + // Send unhandled keyevent to app window. + decor.addOnUnhandledKeyEventListener((View view, KeyEvent event) -> { + switch (event.getKeyCode() ) { + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_ESCAPE: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_DOWN: + return false; + default: + mCallback.dispatchUnhandledKey(event); + return true; + } + }); + if (sVisibleDatasetsMaxCount > 0) { mVisibleDatasetsMaxCount = sVisibleDatasetsMaxCount; if (sVerbose) { @@ -218,14 +215,12 @@ final class FillUi { mFooter = null; mAdapter = null; - // insert authentication item under autofill_dataset_container or decor - ViewGroup container = decor.findViewById(R.id.autofill_dataset_container); - if (container == null) { - container = decor; - } + // insert authentication item under autofill_dataset_picker + ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker); final View content; try { - content = response.getPresentation().apply(context, decor, interceptionHandler); + response.getPresentation().setApplyTheme(THEME_ID); + content = response.getPresentation().apply(mContext, decor, interceptionHandler); container.addView(content); } catch (RuntimeException e) { callback.onCanceled(); @@ -236,20 +231,22 @@ final class FillUi { decor.setFocusable(true); decor.setOnClickListener(v -> mCallback.onResponsePicked(response)); - final Point maxSize = mTempPoint; - resolveMaxWindowSize(context, maxSize); - // fullScreen mode occupy the full width defined by autofill_dataset_picker_max_width - content.getLayoutParams().width = mFullScreen ? maxSize.x - : ViewGroup.LayoutParams.WRAP_CONTENT; - content.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; - final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.x, - MeasureSpec.AT_MOST); - final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.y, - MeasureSpec.AT_MOST); + if (!mFullScreen) { + final Point maxSize = mTempPoint; + resolveMaxWindowSize(mContext, maxSize); + // fullScreen mode occupy the full width defined by autofill_dataset_picker_max_width + content.getLayoutParams().width = mFullScreen ? maxSize.x + : ViewGroup.LayoutParams.WRAP_CONTENT; + content.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.x, + MeasureSpec.AT_MOST); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.y, + MeasureSpec.AT_MOST); - decor.measure(widthMeasureSpec, heightMeasureSpec); - mContentWidth = content.getMeasuredWidth(); - mContentHeight = content.getMeasuredHeight(); + decor.measure(widthMeasureSpec, heightMeasureSpec); + mContentWidth = content.getMeasuredWidth(); + mContentHeight = content.getMeasuredHeight(); + } mWindow = new AnchoredWindow(decor, overlayControl); requestShowFillUi(); @@ -263,7 +260,8 @@ final class FillUi { RemoteViews.OnClickHandler clickBlocker = null; if (headerPresentation != null) { clickBlocker = newClickBlocker(); - mHeader = headerPresentation.apply(context, null, clickBlocker); + headerPresentation.setApplyTheme(THEME_ID); + mHeader = headerPresentation.apply(mContext, null, clickBlocker); final LinearLayout headerContainer = decor.findViewById(R.id.autofill_dataset_header); if (sVerbose) Slog.v(TAG, "adding header"); @@ -274,15 +272,21 @@ final class FillUi { } if (footerPresentation != null) { - if (clickBlocker == null) { // already set for header - clickBlocker = newClickBlocker(); - } - mFooter = footerPresentation.apply(context, null, clickBlocker); final LinearLayout footerContainer = decor.findViewById(R.id.autofill_dataset_footer); - if (sVerbose) Slog.v(TAG, "adding footer"); - footerContainer.addView(mFooter); - footerContainer.setVisibility(View.VISIBLE); + if (footerContainer != null) { + if (clickBlocker == null) { // already set for header + clickBlocker = newClickBlocker(); + } + footerPresentation.setApplyTheme(THEME_ID); + mFooter = footerPresentation.apply(mContext, null, clickBlocker); + // Footer not supported on some platform e.g. TV + if (sVerbose) Slog.v(TAG, "adding footer"); + footerContainer.addView(mFooter); + footerContainer.setVisibility(View.VISIBLE); + } else { + mFooter = null; + } } else { mFooter = null; } @@ -301,7 +305,8 @@ final class FillUi { final View view; try { if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId); - view = presentation.apply(context, null, interceptionHandler); + presentation.setApplyTheme(THEME_ID); + view = presentation.apply(mContext, null, interceptionHandler); } catch (RuntimeException e) { Slog.e(TAG, "Error inflating remote views", e); continue; @@ -352,12 +357,7 @@ final class FillUi { } void requestShowFillUi() { - if (mFullScreen) { - mCallback.requestShowFillUi(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, - mWindowPresenter); - } else { - mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); - } + mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); } /** @@ -388,12 +388,6 @@ final class FillUi { mCallback.requestHideFillUi(); } else { if (updateContentSize()) { - if (mFullScreen) { - LayoutParams lp = mListView.getLayoutParams(); - lp.width = mContentWidth; - lp.height = mContentHeight; - mListView.setLayoutParams(lp); - } requestShowFillUi(); } if (mAdapter.getCount() > mVisibleDatasetsMaxCount) { @@ -452,6 +446,10 @@ final class FillUi { if (mAdapter == null) { return false; } + if (mFullScreen) { + // always request show fill window with fixed size for fullscreen + return true; + } boolean changed = false; if (mAdapter.getCount() <= 0) { if (mContentWidth != 0) { @@ -476,11 +474,6 @@ final class FillUi { final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.y, MeasureSpec.AT_MOST); final int itemCount = mAdapter.getCount(); - if (mFullScreen) { - // fullScreen mode occupy the full width defined by autofill_dataset_picker_max_width - changed = true; - mContentWidth = maxSize.x; - } if (mHeader != null) { mHeader.measure(widthMeasureSpec, heightMeasureSpec); @@ -491,20 +484,9 @@ final class FillUi { for (int i = 0; i < itemCount; i++) { final View view = mAdapter.getItem(i).view; view.measure(widthMeasureSpec, heightMeasureSpec); - if (mFullScreen) { - // for fullscreen, add up all children height until hit max height. - final int newContentHeight = mContentHeight + view.getMeasuredHeight(); - final int clampedNewHeight = Math.min(newContentHeight, maxSize.y); - if (clampedNewHeight != mContentHeight) { - mContentHeight = clampedNewHeight; - } else if (view.getMeasuredHeight() > 0) { - break; - } - } else { - changed |= updateWidth(view, maxSize); - if (i < mVisibleDatasetsMaxCount) { - changed |= updateHeight(view, maxSize); - } + changed |= updateWidth(view, maxSize); + if (i < mVisibleDatasetsMaxCount) { + changed |= updateHeight(view, maxSize); } } diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index f96fa7c237f7..80903c1a1fe4 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -42,6 +42,7 @@ import android.text.Html; import android.util.ArraySet; import android.util.Pair; import android.util.Slog; +import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -70,6 +71,9 @@ final class SaveUi { private static final String TAG = "AutofillSaveUi"; + private static final int THEME_ID = + com.android.internal.R.style.Theme_DeviceDefault_Autofill_Save; + public interface OnSaveListener { void onSave(); void onCancel(IntentSender listener); @@ -144,6 +148,7 @@ final class SaveUi { mServicePackageName = servicePackageName; mPackageName = packageName; + context = new ContextThemeWrapper(context, THEME_ID); final LayoutInflater inflater = LayoutInflater.from(context); final View view = inflater.inflate(R.layout.autofill_save, null); @@ -222,7 +227,7 @@ final class SaveUi { final View yesButton = view.findViewById(R.id.autofill_save_yes); yesButton.setOnClickListener((v) -> mListener.onSave()); - mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel); + mDialog = new Dialog(context, THEME_ID); mDialog.setContentView(view); // Dialog can be dismissed when touched outside, but the negative listener should not be @@ -309,6 +314,7 @@ final class SaveUi { try { // Create the remote view peer. + template.setApplyTheme(THEME_ID); final View customSubtitleView = template.apply(context, null, handler); // And apply batch updates (if any).