From 3922e6a8aa0072bc6048f646fdd6a4732bf04450 Mon Sep 17 00:00:00 2001 From: Jason Long Date: Fri, 27 Jan 2017 15:21:12 -0800 Subject: [PATCH] Add user picker for anchored window. Test: CtsAutoFillServiceTestCases Bug: 34633695 Change-Id: I274264846358941983183a32f07cade3b26c8c05 --- core/res/res/values/dimens.xml | 6 +- core/res/res/values/symbols.xml | 2 + .../server/autofill/AnchoredWindow.java | 9 + .../android/server/autofill/AutoFillUI.java | 26 ++- .../server/autofill/DatasetPicker.java | 196 ++++++++---------- 5 files changed, 126 insertions(+), 113 deletions(-) diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4266f887abe31..927988f712832 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -174,7 +174,7 @@ 16dp - + 45.5dp @@ -518,4 +518,8 @@ 20dp 120dp 800dp + + + 2dp + 64dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 554e123c64ff2..e661957129cf5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2830,6 +2830,8 @@ + + diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java index e674309079dd7..ecfd9b3dd6f7a 100644 --- a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java +++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java @@ -28,6 +28,7 @@ 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. */ @@ -114,6 +115,14 @@ final class AnchoredWindow { return 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); + } + /** FrameLayout that listens for touch events removes itself if the touch event is outside. */ private final class SelfRemovingView extends FrameLayout { public SelfRemovingView(Context context) { diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index 511d3d98d666c..96f340840542c 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -18,6 +18,7 @@ package com.android.server.autofill; import static com.android.server.autofill.Helper.DEBUG; +import android.annotation.Nullable; import android.app.Activity; import android.app.Notification; import android.app.Notification.Action; @@ -38,6 +39,7 @@ import android.view.autofill.Dataset; import android.view.autofill.FillResponse; import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Toast; @@ -60,6 +62,9 @@ final class AutoFillUI { private final WindowManager mWm; + @Nullable + private AnchoredWindow mFillWindow; + /** * Custom snackbar UI used for saving autofill or other informational messages. */ @@ -103,9 +108,23 @@ final class AutoFillUI { void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds, FillResponse response) { if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId + ", bounds=" + bounds); - // TODO(b/33197203): proper implementation - // TODO(b/33197203): make sure if removes the session from cache - showOptionsNotification(userId, sessionId, autoFillId, response); + + UiThread.getHandler().runWithScissors(() -> { + if (mFillWindow != null) { + mFillWindow.hide(); + } + + final DatasetPicker fillView = new DatasetPicker(mContext, response.getDatasets(), + (dataset) -> { + mFillWindow.hide(); + onDatasetPicked(userId, dataset, sessionId); + }); + + // TODO(b/33197203): request width/height properly. + mFillWindow = new AnchoredWindow(mWm, fillView, 800, + ViewGroup.LayoutParams.WRAP_CONTENT); + mFillWindow.show(bounds != null ? bounds : new Rect()); + }, 0); } /** @@ -180,6 +199,7 @@ final class AutoFillUI { final String prefix = " "; pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode); pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar); + mFillWindow.dump(pw); } private AutoFillManagerServiceImpl getServiceLocked(int userId) { diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java index bb641787a6d65..7245aaabfd306 100644 --- a/services/autofill/java/com/android/server/autofill/DatasetPicker.java +++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java @@ -13,127 +13,105 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.server.autofill; -import static com.android.server.autofill.Helper.DEBUG; - -import android.app.Activity; import android.content.Context; -import android.util.Slog; -import android.view.View; -import android.view.WindowManager; +import android.graphics.Color; import android.view.autofill.Dataset; -import android.widget.ImageView; -import android.widget.LinearLayout; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Filter.FilterListener; +import android.widget.ListView; import android.widget.TextView; +import java.util.ArrayList; import java.util.List; /** - * View responsible for drawing the {@link Dataset} options that can be used to auto-fill an - * {@link Activity}. + * View for dataset picker. + * + *

A fill session starts when a View is clicked and FillResponse is supplied. + *

A fill session ends when 1) the user takes action in the UI, 2) another View is clicked, or + * 3) the View is detached. */ -final class DatasetPicker extends LinearLayout { - +final class DatasetPicker extends ListView implements OnItemClickListener { private static final String TAG = "DatasetPicker"; - // TODO(b/33197203): use / calculate proper values instead of hardcoding them - private static final LayoutParams NAME_PARAMS = new LayoutParams(400, - WindowManager.LayoutParams.WRAP_CONTENT); - private static final LayoutParams DROP_DOWN_PARAMS = new LayoutParams(100, - WindowManager.LayoutParams.WRAP_CONTENT); - - private final Line[] mLines; - - private boolean mExpanded; - private final Listener mListener; - - public DatasetPicker(Context context, Listener listener, List datasets) { - super(context); - - mListener = listener; - - // TODO(b/33197203): use XML layout - setOrientation(LinearLayout.VERTICAL); - - final int size = datasets.size(); - mLines = new Line[size]; - - for (int i = 0; i < size; i++) { - final boolean first = i == 0; - final Line line = new Line(context, datasets.get(i), first); - mLines[i] = line; - if (first) { - addView(line); - } - } - mExpanded = false; - } - - private void togleDropDown() { - if (mExpanded) { - hideDropDown(); - return; - } - for (int i = 1; i < mLines.length; i++) { - addView(mLines[i]); - } - mExpanded = true; - } - - private void hideDropDown() { - if (!mExpanded) return; - // TODO(b/33197203): invert order to be less janky? - for (int i = 1; i < mLines.length; i++) { - removeView(mLines[i]); - } - mExpanded = false; - } - - private class Line extends LinearLayout { - final TextView name; - final ImageView dropDown; - - private Line(Context context, Dataset dataset, boolean first) { - super(context); - - final View.OnClickListener l = new View.OnClickListener() { - - @Override - public void onClick(View v) { - if (DEBUG) Slog.d(TAG, "dataset picked: " + dataset.getName()); - mListener.onDatasetPicked(dataset); - - } - }; - - // TODO(b/33197203): use XML layout - setOrientation(LinearLayout.HORIZONTAL); - - name = new TextView(context); - name.setLayoutParams(NAME_PARAMS); - name.setText(dataset.getName()); - name.setOnClickListener(l); - - dropDown = new ImageView(context); - dropDown.setLayoutParams(DROP_DOWN_PARAMS); - // TODO(b/33197203): use proper icon - dropDown.setImageResource(com.android.internal.R.drawable.arrow_down_float); - dropDown.setOnClickListener((v) -> { - togleDropDown(); - }); - - if (!first) { - dropDown.setVisibility(View.INVISIBLE); - } - - addView(name); - addView(dropDown); - } - } - - static interface Listener { + interface Listener { void onDatasetPicked(Dataset dataset); } + + private final Listener mListener; + + DatasetPicker(Context context, List datasets, Listener listener) { + super(context); + mListener = listener; + + final List items = new ArrayList<>(datasets.size()); + for (Dataset dataset : datasets) { + items.add(new ViewItem(dataset)); + } + + final ArrayAdapter adapter = new ArrayAdapter( + context, + android.R.layout.simple_list_item_1, + android.R.id.text1, + items) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final TextView textView = (TextView) super.getView(position, convertView, parent); + textView.setMinHeight( + getDimen(com.android.internal.R.dimen.autofill_fill_item_height)); + return textView; + } + }; + setAdapter(adapter); + setBackgroundColor(Color.WHITE); + setDivider(null); + setElevation(getDimen(com.android.internal.R.dimen.autofill_fill_elevation)); + setOnItemClickListener(this); + } + + public void update(String prefix) { + final ArrayAdapter adapter = (ArrayAdapter) getAdapter(); + adapter.getFilter().filter(prefix, new FilterListener() { + @Override + public void onFilterComplete(int count) { + DatasetPicker.this.requestLayout(); + } + }); + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int pos, long id) { + if (mListener != null) { + final ViewItem vi = (ViewItem) adapterView.getItemAtPosition(pos); + mListener.onDatasetPicked(vi.getData()); + } + } + + private int getDimen(int resId) { + return getContext().getResources().getDimensionPixelSize(resId); + } + + private static class ViewItem { + private final Dataset mData; + + ViewItem(Dataset data) { + mData = data; + } + + public Dataset getData() { + return mData; + } + + @Override + public String toString() { + // used by ArrayAdapter + return mData.getName().toString(); + } + } }