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
This commit is contained in:
Dake Gu 2018-04-16 12:49:30 -07:00
parent 7f352dbeaf
commit 36b86c28f8
22 changed files with 394 additions and 237 deletions

View File

@ -82,6 +82,9 @@ public final class BatchUpdates implements Parcelable {
* {@link #transformChild(int, Transformation) transformations} are applied to the children
* views.
*
* <p>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.
*

View File

@ -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.
*
* <p>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

View File

@ -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.
*
* <p>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).
*
* <p>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
* &mdash;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).
*
* <p>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
* &mdash;calling
* {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would

View File

@ -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!");

View File

@ -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);

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- NOTE: outer layout is required to provide proper shadow. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:id="@+id/autofill_save"
android:background="?android:attr/colorBackground"
android:layout_marginTop="32dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="40dp"
android:paddingBottom="40dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="32dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp">
<ImageView
android:id="@+id/autofill_save_icon"
android:scaleType="fitStart"
android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical"
android:layout_width="48dp"
android:layout_height="48dp"/>
<TextView
android:id="@+id/autofill_save_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/autofill_save_title"
android:layout_gravity="center_vertical"
android:textSize="24sp" />
</LinearLayout>
<com.android.server.autofill.ui.CustomScrollView
android:id="@+id/autofill_save_custom_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"/>
</LinearLayout>
<LinearLayout
android:layout_width="304dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/autofill_save_no"
style="?attr/borderlessButtonStyle"
android:textAlignment="viewStart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/autofill_save_no">
</Button>
<Button
android:id="@+id/autofill_save_yes"
style="?attr/borderlessButtonStyle"
android:textAlignment="viewStart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/autofill_save_yes">
</Button>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -14,8 +14,7 @@
limitations under the License.
-->
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.server.autofill.ui.FillUi$AutofillFrameLayout"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/autofill_dataset_picker"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@ -31,4 +30,4 @@
android:visibility="gone">
</ListView>
</view>
</FrameLayout>

View File

@ -14,35 +14,73 @@
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/autofill_dataset_picker"
style="@style/AutofillDatasetPicker"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:background="?android:attr/windowBackground"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="40dp"
android:paddingBottom="40dp">
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/autofill_window_title"
android:layout_above="@+id/autofill_dataset_container"
android:layout_alignStart="@+id/autofill_dataset_container"
android:textSize="16sp"/>
android:layout_weight="1"
android:layout_marginEnd="32dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
>
<ImageView
android:id="@+id/autofill_dataset_icon"
android:scaleType="fitStart"
android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical"
android:layout_width="48dp"
android:layout_height="48dp"/>
<TextView
android:id="@+id/autofill_dataset_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/autofill_dataset_header"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
<!-- autofill_container is the common parent for inserting authentication item or
autofill_dataset_list-->
<FrameLayout
android:id="@+id/autofill_dataset_container"
android:layout_width="wrap_content"
autofill_dataset_list, autofill_dataset_foolter-->
<LinearLayout
android:layout_width="304dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
android:id="@+id/autofill_dataset_picker"
android:orientation="vertical">
<ListView
android:id="@+id/autofill_dataset_list"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:divider="@null"
android:drawSelectorOnTop="true"
android:visibility="gone"/>
</FrameLayout>
<LinearLayout
android:id="@+id/autofill_dataset_footer"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -14,8 +14,7 @@
limitations under the License.
-->
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.server.autofill.ui.FillUi$AutofillFrameLayout"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/autofill_dataset_picker"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@ -54,4 +53,4 @@
</LinearLayout>
</view>
</FrameLayout>

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/autofill_dataset_picker"
style="@style/AutofillDatasetPicker"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/autofill_window_title"
android:layout_above="@+id/autofill_dataset_container"
android:layout_alignStart="@+id/autofill_dataset_container"
android:textSize="16sp"/>
<!-- autofill_container is the common parent for inserting authentication item or
autofill_dataset_list-->
<FrameLayout
android:id="@+id/autofill_dataset_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/autofill_dataset_header"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<ListView
android:id="@+id/autofill_dataset_list"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:clickable="true"
android:divider="@null"
android:drawSelectorOnTop="true"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/autofill_dataset_footer"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
</FrameLayout>
</RelativeLayout>

View File

@ -20,8 +20,4 @@
<item type="dimen" format="float" name="ambient_shadow_alpha">0.15</item>
<item type="dimen" format="float" name="spot_shadow_alpha">0.3</item>
<!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
<dimen name="autofill_dataset_picker_max_width">60%</dimen>
<dimen name="autofill_dataset_picker_max_height">70%</dimen>
</resources>

View File

@ -16,4 +16,6 @@
<resources>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" />
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
<style name="Theme.DeviceDefault.Autofill" parent="Theme.Material.Autofill" />
<style name="Theme.DeviceDefault.Autofill.Save" parent="Theme.Material.Autofill.Save" />
</resources>

View File

@ -145,4 +145,8 @@
<color name="datepicker_default_view_animator_color_material_light">#fff2f2f2</color>
<color name="datepicker_default_view_animator_color_material_dark">#ff303030</color>
<!-- Autofill colors -->
<color name="autofill_background_material_dark">@color/material_blue_grey_900</color>
<color name="autofill_background_material_light">@color/material_grey_50</color>
</resources>

View File

@ -2196,8 +2196,8 @@
<!-- Text to show in the auto complete drop down list on a text view when the WebView can auto fill the entire form but the user has not configured an AutoFill profile [CHAR-LIMIT=19] -->
<string name="setup_autofill">Set up Autofill</string>
<!-- Title of fullscreen autofill window [CHAR-LIMIT=80] -->
<string name="autofill_window_title">Autofill</string>
<!-- Title of fullscreen autofill window, including the name of which autofill service it is using [CHAR-LIMIT=NONE] -->
<string name="autofill_window_title">Autofill with <xliff:g id="serviceName" example="MyPass">%1$s</xliff:g></string>
<!-- String used to separate FirstName and LastName when writing out a local name
e.g. John<separator>Smith [CHAR-LIMIT=NONE]-->

View File

@ -1487,12 +1487,18 @@ please see styles_device_defaults.xml.
<item name="successColor">@color/lock_pattern_view_success_color</item>
</style>
<!-- @hide -->
<!-- @hide Autofill background for popup window (not for fullscreen) -->
<style name="AutofillDatasetPicker">
<item name="elevation">4dp</item>
<item name="background">@drawable/autofill_dataset_picker_background</item>
</style>
<!-- @hide -->
<style name="AutofillHalfScreenAnimation">
<item name="android:windowEnterAnimation">@anim/slide_in_up</item>
<item name="android:windowExitAnimation">@anim/slide_out_down</item>
</style>
<!-- @hide -->
<style name="AutofillSaveAnimation">
<item name="android:windowEnterAnimation">@anim/slide_in_up</item>

View File

@ -662,6 +662,7 @@
<java-symbol type="string" name="autofill_state_re" />
<java-symbol type="string" name="autofill_this_form" />
<java-symbol type="string" name="autofill_username_re" />
<java-symbol type="string" name="autofill_window_title" />
<java-symbol type="string" name="autofill_zip_4_re" />
<java-symbol type="string" name="autofill_zip_code" />
<java-symbol type="string" name="autofill_zip_code_re" />
@ -3045,13 +3046,13 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer_fullscreen"/>
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_container"/>
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
<java-symbol type="id" name="autofill_dataset_icon" />
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_title" />
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
<java-symbol type="id" name="autofill_save_no" />
@ -3076,6 +3077,7 @@
<java-symbol type="string" name="autofill_save_type_email_address" />
<java-symbol type="drawable" name="autofill_dataset_picker_background" />
<java-symbol type="style" name="AutofillDatasetPicker" />
<java-symbol type="style" name="AutofillHalfScreenAnimation" />
<java-symbol type="style" name="AutofillSaveAnimation" />
<java-symbol type="dimen" name="autofill_dataset_picker_max_width"/>
<java-symbol type="dimen" name="autofill_dataset_picker_max_height"/>
@ -3083,6 +3085,9 @@
<java-symbol type="dimen" name="autofill_save_icon_max_size"/>
<java-symbol type="integer" name="autofill_max_visible_datasets" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill.Save" />
<java-symbol type="dimen" name="notification_big_picture_max_height"/>
<java-symbol type="dimen" name="notification_big_picture_max_width"/>
<java-symbol type="dimen" name="notification_media_image_max_width"/>

View File

@ -1673,6 +1673,15 @@ easier.
<item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
</style>
<!-- @hide DeviceDefault theme for the autofill FillUi -->
<style name="Theme.DeviceDefault.Autofill" parent="Theme.Material.Autofill.Light">
</style>
<!-- @hide DeviceDefault theme for the autofill SaveUi -->
<style name="Theme.DeviceDefault.Autofill.Save" parent="Theme.Material.Autofill.Save.Light">
</style>
<!-- DeviceDefault theme for the default system theme. -->
<style name="Theme.DeviceDefault.System" parent="Theme.DeviceDefault.Light.DarkActionBar" />

View File

@ -1417,4 +1417,25 @@ please see themes_device_defaults.xml.
<item name="colorPrimaryDark">@color/primary_dark_material_settings</item>
<item name="colorSecondary">@color/secondary_material_settings</item>
</style>
<!-- @hide -->
<style name="Theme.Material.Autofill" parent="Theme.Material">
<item name="colorBackground">@color/autofill_background_material_dark</item>
</style>
<!-- @hide -->
<style name="Theme.Material.Autofill.Light" parent="Theme.Material.Light">
<item name="colorBackground">@color/autofill_background_material_light</item>
</style>
<!-- @hide -->
<style name="Theme.Material.Autofill.Save" parent="Theme.Material.Panel">
<item name="colorBackground">@color/autofill_background_material_dark</item>
</style>
<!-- @hide -->
<style name="Theme.Material.Autofill.Save.Light" parent="Theme.Material.Light.Panel">
<item name="colorBackground">@color/autofill_background_material_light</item>
</style>
</resources>

View File

@ -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) {

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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).