Loading api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37480,6 +37480,8 @@ package android.service.autofill { method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setFlags(int); method public android.service.autofill.FillResponse.Builder setFooter(android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setHeader(android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } core/java/android/service/autofill/FillResponse.java +104 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,8 @@ public final class FillResponse implements Parcelable { private final @Nullable SaveInfo mSaveInfo; private final @Nullable Bundle mClientState; private final @Nullable RemoteViews mPresentation; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; Loading @@ -85,6 +87,8 @@ public final class FillResponse implements Parcelable { mSaveInfo = builder.mSaveInfo; mClientState = builder.mClientState; mPresentation = builder.mPresentation; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; Loading Loading @@ -114,6 +118,16 @@ public final class FillResponse implements Parcelable { return mPresentation; } /** @hide */ public @Nullable RemoteViews getHeader() { return mHeader; } /** @hide */ public @Nullable RemoteViews getFooter() { return mFooter; } /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; Loading Loading @@ -171,6 +185,8 @@ public final class FillResponse implements Parcelable { private SaveInfo mSaveInfo; private Bundle mClientState; private RemoteViews mPresentation; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; Loading Loading @@ -226,16 +242,24 @@ public final class FillResponse implements Parcelable { * @param ids id of Views that when focused will display the authentication UI. * * @return This builder. * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if * both {@code authentication} and {@code presentation} are {@code null}, or if * both {@code authentication} and {@code presentation} are non-{@code null} * * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a * {@link #setFooter(RemoteViews) footer} are already set for this builder. * * @see android.app.PendingIntent#getIntentSender() */ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { throw new IllegalStateException("Already called #setHeader() or #setFooter()"); } if (ids == null || ids.length == 0) { throw new IllegalArgumentException("ids cannot be null or empry"); } Loading Loading @@ -417,6 +441,62 @@ public final class FillResponse implements Parcelable { return this; } /** * Sets a header to be shown as the first element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the header could have been set directly in the main presentation in * these cases). * * @param header a presentation to represent the header. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if an * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was * already set for this builder. */ // TODO(b/69796626): make it sticky / update javadoc public Builder setHeader(@NonNull RemoteViews header) { throwIfDestroyed(); throwIfAuthenticationCalled(); mHeader = Preconditions.checkNotNull(header); return this; } /** * Sets a footer to be shown as the last element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the footer could have been set directly in the main presentation in * these cases). * * @param footer a presentation to represent the footer. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if the FillResponse * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) * requires authentication}. */ // TODO(b/69796626): make it sticky / update javadoc public Builder setFooter(@NonNull RemoteViews footer) { throwIfDestroyed(); throwIfAuthenticationCalled(); mFooter = Preconditions.checkNotNull(footer); return this; } /** * Builds a new {@link FillResponse} instance. * Loading @@ -428,6 +508,8 @@ public final class FillResponse implements Parcelable { * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, * {@link #setClientState(Bundle)}, * or {link #setFieldClassificationIds(AutofillId...)}. * <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called * without any previous calls to {@link #addDataset(Dataset)}. * </ol> * * @return A built response. Loading @@ -442,6 +524,10 @@ public final class FillResponse implements Parcelable { + "SaveInfo, or an authentication with a presentation, " + "or a FieldsDetection, or a client state, or disable autofill"); } if (mDatasets == null && (mHeader != null || mFooter != null)) { throw new IllegalStateException( "must add at least 1 dataset when using header or footer"); } mDestroyed = true; return new FillResponse(this); } Loading @@ -457,6 +543,12 @@ public final class FillResponse implements Parcelable { throw new IllegalStateException("Already called #disableAutofill()"); } } private void throwIfAuthenticationCalled() { if (mAuthentication != null) { throw new IllegalStateException("Already called #setAuthentication()"); } } } ///////////////////////////////////// Loading @@ -473,6 +565,8 @@ public final class FillResponse implements Parcelable { .append(", saveInfo=").append(mSaveInfo) .append(", clientState=").append(mClientState != null) .append(", hasPresentation=").append(mPresentation != null) .append(", hasHeader=").append(mHeader != null) .append(", hasFooter=").append(mFooter != null) .append(", hasAuthentication=").append(mAuthentication != null) .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) Loading Loading @@ -501,6 +595,8 @@ public final class FillResponse implements Parcelable { parcel.writeParcelableArray(mAuthenticationIds, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); parcel.writeParcelableArray(mFieldClassificationIds, flags); Loading Loading @@ -533,6 +629,14 @@ public final class FillResponse implements Parcelable { if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation); } final RemoteViews header = parcel.readParcelable(null); if (header != null) { builder.setHeader(header); } final RemoteViews footer = parcel.readParcelable(null); if (footer != null) { builder.setFooter(footer); } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); final long disableDuration = parcel.readLong(); Loading core/res/res/values/symbols.xml +1 −1 Original line number Diff line number Diff line Loading @@ -2932,8 +2932,8 @@ <!-- com.android.server.autofill --> <java-symbol type="layout" name="autofill_save"/> <java-symbol type="layout" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_list"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_save_custom_subtitle" /> <java-symbol type="id" name="autofill_save_icon" /> Loading services/autofill/java/com/android/server/autofill/ui/FillUi.java +79 −37 Original line number Diff line number Diff line Loading @@ -108,9 +108,11 @@ final class FillUi { mCallback = callback; final LayoutInflater inflater = LayoutInflater.from(context); final ViewGroup decor = (ViewGroup) inflater.inflate( R.layout.autofill_dataset_picker, null); final RemoteViews.OnClickHandler interceptionHandler = new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler(View view, PendingIntent pendingIntent, Loading Loading @@ -153,7 +155,38 @@ final class FillUi { mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); } else { final int datasetCount = response.getDatasets().size(); final ArrayList<ViewItem> items = new ArrayList<>(datasetCount); // Total items include the (optional) header and footer - we cannot use listview's // addHeader() and addFooter() because it would complicate the scrolling logic. int totalItems = datasetCount; RemoteViews.OnClickHandler clickBlocker = null; final RemoteViews headerPresentation = response.getHeader(); View header = null; if (headerPresentation != null) { clickBlocker = newClickBlocker(); header = headerPresentation.apply(context, null, clickBlocker); totalItems++; } final RemoteViews footerPresentation = response.getFooter(); View footer = null; if (footerPresentation != null) { if (clickBlocker == null) { // already set for header clickBlocker = newClickBlocker(); } footer = footerPresentation.apply(context, null, clickBlocker); totalItems++; } if (sVerbose) { Slog.v(TAG, "Number datasets: " + datasetCount + " Total items: " + totalItems); } final ArrayList<ViewItem> items = new ArrayList<>(totalItems); if (header != null) { if (sVerbose) Slog.v(TAG, "adding header"); items.add(new ViewItem(null, null, null, header)); } for (int i = 0; i < datasetCount; i++) { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); Loading Loading @@ -184,6 +217,10 @@ final class FillUi { items.add(new ViewItem(dataset, filter, valueText, view)); } } if (footer != null) { if (sVerbose) Slog.v(TAG, "adding footer"); items.add(new ViewItem(null, null, null, footer)); } mAdapter = new ItemsAdapter(items); Loading @@ -192,7 +229,12 @@ final class FillUi { mListView.setVisibility(View.VISIBLE); mListView.setOnItemClickListener((adapter, view, position, id) -> { final ViewItem vi = mAdapter.getItem(position); mCallback.onDatasetPicked(vi.getDataset()); if (vi.dataset == null) { // Clicked on header or footer; ignore. if (sDebug) Slog.d(TAG, "Ignoring click on item " + position + ": " + view); return; } mCallback.onDatasetPicked(vi.dataset); }); if (filterText == null) { Loading @@ -206,6 +248,20 @@ final class FillUi { } } /** * Creates a remoteview interceptor used to block clicks. */ private RemoteViews.OnClickHandler newClickBlocker() { return new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { if (sVerbose) Slog.v(TAG, "Ignoring click on " + view); return true; } }; } private void applyNewFilterText() { final int oldCount = mAdapter.getCount(); mAdapter.getFilter().filter(mFilterText, (count) -> { Loading Loading @@ -298,7 +354,7 @@ final class FillUi { MeasureSpec.AT_MOST); final int itemCount = mAdapter.getCount(); for (int i = 0; i < itemCount; i++) { View view = mAdapter.getItem(i).getView(); View view = mAdapter.getItem(i).view; view.measure(widthMeasureSpec, heightMeasureSpec); final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x); final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth); Loading Loading @@ -336,33 +392,21 @@ final class FillUi { outPoint.y = (int) typedValue.getFraction(outPoint.y, outPoint.y); } /** * An item for the list view - either a (clickable) dataset or a (read-only) header / footer. */ private static class ViewItem { private final String mValue; private final Dataset mDataset; private final View mView; private final Pattern mFilter; ViewItem(Dataset dataset, Pattern filter, String value, View view) { mDataset = dataset; mValue = value; mView = view; mFilter = filter; } public Pattern getFilter() { return mFilter; } public View getView() { return mView; } public Dataset getDataset() { return mDataset; } public final @Nullable String value; public final @Nullable Dataset dataset; public final @NonNull View view; public final @Nullable Pattern filter; public String getValue() { return mValue; ViewItem(@Nullable Dataset dataset, @Nullable Pattern filter, @Nullable String value, @NonNull View view) { this.dataset = dataset; this.value = value; this.view = view; this.filter = filter; } } Loading Loading @@ -525,15 +569,13 @@ final class FillUi { final int itemCount = mAllItems.size(); for (int i = 0; i < itemCount; i++) { final ViewItem item = mAllItems.get(i); final String value = item.getValue(); final Pattern filter = item.getFilter(); final boolean matches; if (filter != null) { matches = filter.matcher(constraintLowerCase).matches(); if (item.filter != null) { matches = item.filter.matcher(constraintLowerCase).matches(); } else { matches = (value == null) ? (item.mDataset.getAuthentication() == null) : value.toLowerCase().startsWith(constraintLowerCase); matches = (item.value == null) ? (item.dataset.getAuthentication() == null) : item.value.toLowerCase().startsWith(constraintLowerCase); } if (matches) { filteredItems.add(item); Loading Loading @@ -580,7 +622,7 @@ final class FillUi { @Override public View getView(int position, View convertView, ViewGroup parent) { return getItem(position).getView(); return getItem(position).view; } } Loading Loading
api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37480,6 +37480,8 @@ package android.service.autofill { method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setFlags(int); method public android.service.autofill.FillResponse.Builder setFooter(android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setHeader(android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); }
core/java/android/service/autofill/FillResponse.java +104 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,8 @@ public final class FillResponse implements Parcelable { private final @Nullable SaveInfo mSaveInfo; private final @Nullable Bundle mClientState; private final @Nullable RemoteViews mPresentation; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; Loading @@ -85,6 +87,8 @@ public final class FillResponse implements Parcelable { mSaveInfo = builder.mSaveInfo; mClientState = builder.mClientState; mPresentation = builder.mPresentation; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; Loading Loading @@ -114,6 +118,16 @@ public final class FillResponse implements Parcelable { return mPresentation; } /** @hide */ public @Nullable RemoteViews getHeader() { return mHeader; } /** @hide */ public @Nullable RemoteViews getFooter() { return mFooter; } /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; Loading Loading @@ -171,6 +185,8 @@ public final class FillResponse implements Parcelable { private SaveInfo mSaveInfo; private Bundle mClientState; private RemoteViews mPresentation; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; Loading Loading @@ -226,16 +242,24 @@ public final class FillResponse implements Parcelable { * @param ids id of Views that when focused will display the authentication UI. * * @return This builder. * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if * both {@code authentication} and {@code presentation} are {@code null}, or if * both {@code authentication} and {@code presentation} are non-{@code null} * * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a * {@link #setFooter(RemoteViews) footer} are already set for this builder. * * @see android.app.PendingIntent#getIntentSender() */ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { throw new IllegalStateException("Already called #setHeader() or #setFooter()"); } if (ids == null || ids.length == 0) { throw new IllegalArgumentException("ids cannot be null or empry"); } Loading Loading @@ -417,6 +441,62 @@ public final class FillResponse implements Parcelable { return this; } /** * Sets a header to be shown as the first element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the header could have been set directly in the main presentation in * these cases). * * @param header a presentation to represent the header. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if an * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was * already set for this builder. */ // TODO(b/69796626): make it sticky / update javadoc public Builder setHeader(@NonNull RemoteViews header) { throwIfDestroyed(); throwIfAuthenticationCalled(); mHeader = Preconditions.checkNotNull(header); return this; } /** * Sets a footer to be shown as the last element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the footer could have been set directly in the main presentation in * these cases). * * @param footer a presentation to represent the footer. This presentation is not clickable * —calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if the FillResponse * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) * requires authentication}. */ // TODO(b/69796626): make it sticky / update javadoc public Builder setFooter(@NonNull RemoteViews footer) { throwIfDestroyed(); throwIfAuthenticationCalled(); mFooter = Preconditions.checkNotNull(footer); return this; } /** * Builds a new {@link FillResponse} instance. * Loading @@ -428,6 +508,8 @@ public final class FillResponse implements Parcelable { * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, * {@link #setClientState(Bundle)}, * or {link #setFieldClassificationIds(AutofillId...)}. * <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called * without any previous calls to {@link #addDataset(Dataset)}. * </ol> * * @return A built response. Loading @@ -442,6 +524,10 @@ public final class FillResponse implements Parcelable { + "SaveInfo, or an authentication with a presentation, " + "or a FieldsDetection, or a client state, or disable autofill"); } if (mDatasets == null && (mHeader != null || mFooter != null)) { throw new IllegalStateException( "must add at least 1 dataset when using header or footer"); } mDestroyed = true; return new FillResponse(this); } Loading @@ -457,6 +543,12 @@ public final class FillResponse implements Parcelable { throw new IllegalStateException("Already called #disableAutofill()"); } } private void throwIfAuthenticationCalled() { if (mAuthentication != null) { throw new IllegalStateException("Already called #setAuthentication()"); } } } ///////////////////////////////////// Loading @@ -473,6 +565,8 @@ public final class FillResponse implements Parcelable { .append(", saveInfo=").append(mSaveInfo) .append(", clientState=").append(mClientState != null) .append(", hasPresentation=").append(mPresentation != null) .append(", hasHeader=").append(mHeader != null) .append(", hasFooter=").append(mFooter != null) .append(", hasAuthentication=").append(mAuthentication != null) .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) Loading Loading @@ -501,6 +595,8 @@ public final class FillResponse implements Parcelable { parcel.writeParcelableArray(mAuthenticationIds, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); parcel.writeParcelableArray(mFieldClassificationIds, flags); Loading Loading @@ -533,6 +629,14 @@ public final class FillResponse implements Parcelable { if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation); } final RemoteViews header = parcel.readParcelable(null); if (header != null) { builder.setHeader(header); } final RemoteViews footer = parcel.readParcelable(null); if (footer != null) { builder.setFooter(footer); } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); final long disableDuration = parcel.readLong(); Loading
core/res/res/values/symbols.xml +1 −1 Original line number Diff line number Diff line Loading @@ -2932,8 +2932,8 @@ <!-- com.android.server.autofill --> <java-symbol type="layout" name="autofill_save"/> <java-symbol type="layout" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_list"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_save_custom_subtitle" /> <java-symbol type="id" name="autofill_save_icon" /> Loading
services/autofill/java/com/android/server/autofill/ui/FillUi.java +79 −37 Original line number Diff line number Diff line Loading @@ -108,9 +108,11 @@ final class FillUi { mCallback = callback; final LayoutInflater inflater = LayoutInflater.from(context); final ViewGroup decor = (ViewGroup) inflater.inflate( R.layout.autofill_dataset_picker, null); final RemoteViews.OnClickHandler interceptionHandler = new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler(View view, PendingIntent pendingIntent, Loading Loading @@ -153,7 +155,38 @@ final class FillUi { mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); } else { final int datasetCount = response.getDatasets().size(); final ArrayList<ViewItem> items = new ArrayList<>(datasetCount); // Total items include the (optional) header and footer - we cannot use listview's // addHeader() and addFooter() because it would complicate the scrolling logic. int totalItems = datasetCount; RemoteViews.OnClickHandler clickBlocker = null; final RemoteViews headerPresentation = response.getHeader(); View header = null; if (headerPresentation != null) { clickBlocker = newClickBlocker(); header = headerPresentation.apply(context, null, clickBlocker); totalItems++; } final RemoteViews footerPresentation = response.getFooter(); View footer = null; if (footerPresentation != null) { if (clickBlocker == null) { // already set for header clickBlocker = newClickBlocker(); } footer = footerPresentation.apply(context, null, clickBlocker); totalItems++; } if (sVerbose) { Slog.v(TAG, "Number datasets: " + datasetCount + " Total items: " + totalItems); } final ArrayList<ViewItem> items = new ArrayList<>(totalItems); if (header != null) { if (sVerbose) Slog.v(TAG, "adding header"); items.add(new ViewItem(null, null, null, header)); } for (int i = 0; i < datasetCount; i++) { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); Loading Loading @@ -184,6 +217,10 @@ final class FillUi { items.add(new ViewItem(dataset, filter, valueText, view)); } } if (footer != null) { if (sVerbose) Slog.v(TAG, "adding footer"); items.add(new ViewItem(null, null, null, footer)); } mAdapter = new ItemsAdapter(items); Loading @@ -192,7 +229,12 @@ final class FillUi { mListView.setVisibility(View.VISIBLE); mListView.setOnItemClickListener((adapter, view, position, id) -> { final ViewItem vi = mAdapter.getItem(position); mCallback.onDatasetPicked(vi.getDataset()); if (vi.dataset == null) { // Clicked on header or footer; ignore. if (sDebug) Slog.d(TAG, "Ignoring click on item " + position + ": " + view); return; } mCallback.onDatasetPicked(vi.dataset); }); if (filterText == null) { Loading @@ -206,6 +248,20 @@ final class FillUi { } } /** * Creates a remoteview interceptor used to block clicks. */ private RemoteViews.OnClickHandler newClickBlocker() { return new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { if (sVerbose) Slog.v(TAG, "Ignoring click on " + view); return true; } }; } private void applyNewFilterText() { final int oldCount = mAdapter.getCount(); mAdapter.getFilter().filter(mFilterText, (count) -> { Loading Loading @@ -298,7 +354,7 @@ final class FillUi { MeasureSpec.AT_MOST); final int itemCount = mAdapter.getCount(); for (int i = 0; i < itemCount; i++) { View view = mAdapter.getItem(i).getView(); View view = mAdapter.getItem(i).view; view.measure(widthMeasureSpec, heightMeasureSpec); final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x); final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth); Loading Loading @@ -336,33 +392,21 @@ final class FillUi { outPoint.y = (int) typedValue.getFraction(outPoint.y, outPoint.y); } /** * An item for the list view - either a (clickable) dataset or a (read-only) header / footer. */ private static class ViewItem { private final String mValue; private final Dataset mDataset; private final View mView; private final Pattern mFilter; ViewItem(Dataset dataset, Pattern filter, String value, View view) { mDataset = dataset; mValue = value; mView = view; mFilter = filter; } public Pattern getFilter() { return mFilter; } public View getView() { return mView; } public Dataset getDataset() { return mDataset; } public final @Nullable String value; public final @Nullable Dataset dataset; public final @NonNull View view; public final @Nullable Pattern filter; public String getValue() { return mValue; ViewItem(@Nullable Dataset dataset, @Nullable Pattern filter, @Nullable String value, @NonNull View view) { this.dataset = dataset; this.value = value; this.view = view; this.filter = filter; } } Loading Loading @@ -525,15 +569,13 @@ final class FillUi { final int itemCount = mAllItems.size(); for (int i = 0; i < itemCount; i++) { final ViewItem item = mAllItems.get(i); final String value = item.getValue(); final Pattern filter = item.getFilter(); final boolean matches; if (filter != null) { matches = filter.matcher(constraintLowerCase).matches(); if (item.filter != null) { matches = item.filter.matcher(constraintLowerCase).matches(); } else { matches = (value == null) ? (item.mDataset.getAuthentication() == null) : value.toLowerCase().startsWith(constraintLowerCase); matches = (item.value == null) ? (item.dataset.getAuthentication() == null) : item.value.toLowerCase().startsWith(constraintLowerCase); } if (matches) { filteredItems.add(item); Loading Loading @@ -580,7 +622,7 @@ final class FillUi { @Override public View getView(int position, View convertView, ViewGroup parent) { return getItem(position).getView(); return getItem(position).view; } } Loading