Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9076c1b1 authored by Felipe Leme's avatar Felipe Leme
Browse files

Fixed autofill dataset picker so header and footer are sticky.

Test: manual verification using sample service
Test: atest CtsAutoFillServiceTestCases

Fixes: 69796626
Bug: 77155952

Change-Id: I8049a531b1e12cf94b8588092bdf28ec45bd1b08
parent a1ff74c6
Loading
Loading
Loading
Loading
+57 −0
Original line number Original line Diff line number Diff line
<?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.
-->

<view  xmlns:android="http://schemas.android.com/apk/res/android"
    class="com.android.server.autofill.ui.FillUi$AutofillFrameLayout"
    android:id="@+id/autofill_dataset_picker"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    style="@style/AutofillDatasetPicker">

    <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:drawSelectorOnTop="true"
          android:clickable="true"
          android:divider="@null"
          android:visibility="gone">
      </ListView>

      <LinearLayout
          android:id="@+id/autofill_dataset_footer"
          android:visibility="gone"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical"/>

    </LinearLayout>

</view>
+73 −0
Original line number Original line Diff line number Diff line
<?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>
+4 −0
Original line number Original line Diff line number Diff line
@@ -3019,7 +3019,11 @@
  <java-symbol type="layout" name="autofill_save"/>
  <java-symbol type="layout" name="autofill_save"/>
  <java-symbol type="layout" name="autofill_dataset_picker"/>
  <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_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_dataset_container"/>
  <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_list"/>
  <java-symbol type="id" name="autofill_dataset_list"/>
  <java-symbol type="id" name="autofill_dataset_picker"/>
  <java-symbol type="id" name="autofill_dataset_picker"/>
  <java-symbol type="id" name="autofill" />
  <java-symbol type="id" name="autofill" />
+95 −55
Original line number Original line Diff line number Diff line
@@ -51,6 +51,7 @@ import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ListView;
import android.widget.RemoteViews;
import android.widget.RemoteViews;


@@ -118,7 +119,9 @@ final class FillUi {


    private final @NonNull Callback mCallback;
    private final @NonNull Callback mCallback;


    private final @Nullable View mHeader;
    private final @NonNull ListView mListView;
    private final @NonNull ListView mListView;
    private final @Nullable View mFooter;


    private final @Nullable ItemsAdapter mAdapter;
    private final @Nullable ItemsAdapter mAdapter;


@@ -145,9 +148,18 @@ final class FillUi {


        final LayoutInflater inflater = LayoutInflater.from(context);
        final LayoutInflater inflater = LayoutInflater.from(context);


        final ViewGroup decor = (ViewGroup) inflater.inflate(
        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);
        } else {
            decor = (ViewGroup) inflater.inflate(
                    mFullScreen ? R.layout.autofill_dataset_picker_fullscreen
                    mFullScreen ? R.layout.autofill_dataset_picker_fullscreen
                            : R.layout.autofill_dataset_picker, null);
                            : R.layout.autofill_dataset_picker, null);
        }


        // if autofill ui is not fullscreen, send unhandled keyevent to app window.
        // if autofill ui is not fullscreen, send unhandled keyevent to app window.
        if (!mFullScreen) {
        if (!mFullScreen) {
@@ -186,7 +198,9 @@ final class FillUi {
        };
        };


        if (response.getAuthentication() != null) {
        if (response.getAuthentication() != null) {
            mHeader = null;
            mListView = null;
            mListView = null;
            mFooter = null;
            mAdapter = null;
            mAdapter = null;


            // insert authentication item under autofill_dataset_container or decor
            // insert authentication item under autofill_dataset_container or decor
@@ -207,7 +221,7 @@ final class FillUi {
            decor.setFocusable(true);
            decor.setFocusable(true);
            decor.setOnClickListener(v -> mCallback.onResponsePicked(response));
            decor.setOnClickListener(v -> mCallback.onResponsePicked(response));


            Point maxSize = mTempPoint;
            final Point maxSize = mTempPoint;
            resolveMaxWindowSize(context, maxSize);
            resolveMaxWindowSize(context, maxSize);
            // fullScreen mode occupy the full width defined by autofill_dataset_picker_max_width
            // fullScreen mode occupy the full width defined by autofill_dataset_picker_max_width
            content.getLayoutParams().width = mFullScreen ? maxSize.x
            content.getLayoutParams().width = mFullScreen ? maxSize.x
@@ -226,38 +240,39 @@ final class FillUi {
            requestShowFillUi();
            requestShowFillUi();
        } else {
        } else {
            final int datasetCount = response.getDatasets().size();
            final int datasetCount = response.getDatasets().size();

            if (sVerbose) {
            // Total items include the (optional) header and footer - we cannot use listview's
                Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: "
            // addHeader() and addFooter() because it would complicate the scrolling logic.
                        + sVisibleDatasetsMaxCount);
            int totalItems = datasetCount;
            }


            RemoteViews.OnClickHandler clickBlocker = null;
            RemoteViews.OnClickHandler clickBlocker = null;
            final RemoteViews headerPresentation = response.getHeader();
            View header = null;
            if (headerPresentation != null) {
            if (headerPresentation != null) {
                clickBlocker = newClickBlocker();
                clickBlocker = newClickBlocker();
                header = headerPresentation.apply(context, null, clickBlocker);
                mHeader = headerPresentation.apply(context, null, clickBlocker);
                totalItems++;
                final LinearLayout headerContainer =
                        decor.findViewById(R.id.autofill_dataset_header);
                if (sVerbose) Slog.v(TAG, "adding header");
                headerContainer.addView(mHeader);
                headerContainer.setVisibility(View.VISIBLE);
            } else {
                mHeader = null;
            }
            }


            final RemoteViews footerPresentation = response.getFooter();
            View footer = null;
            if (footerPresentation != null) {
            if (footerPresentation != null) {
                if (clickBlocker == null) { // already set for header
                if (clickBlocker == null) { // already set for header
                    clickBlocker = newClickBlocker();
                    clickBlocker = newClickBlocker();
                }
                }
                footer = footerPresentation.apply(context, null, clickBlocker);
                mFooter = footerPresentation.apply(context, null, clickBlocker);
                totalItems++;
                final LinearLayout footerContainer =
            }
                        decor.findViewById(R.id.autofill_dataset_footer);
            if (sVerbose) {
                if (sVerbose) Slog.v(TAG, "adding footer");
                Slog.v(TAG, "Number datasets: " + datasetCount + " Total items: " + totalItems);
                footerContainer.addView(mFooter);
                footerContainer.setVisibility(View.VISIBLE);
            } else {
                mFooter = null;
            }
            }


            final ArrayList<ViewItem> items = new ArrayList<>(totalItems);
            final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
            if (header != null) {
                if (sVerbose) Slog.v(TAG, "adding header");
                items.add(new ViewItem(null, null, false, null, header));
            }
            for (int i = 0; i < datasetCount; i++) {
            for (int i = 0; i < datasetCount; i++) {
                final Dataset dataset = response.getDatasets().get(i);
                final Dataset dataset = response.getDatasets().get(i);
                final int index = dataset.getFieldIds().indexOf(focusedViewId);
                final int index = dataset.getFieldIds().indexOf(focusedViewId);
@@ -299,10 +314,6 @@ final class FillUi {
                    items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
                    items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
                }
                }
            }
            }
            if (footer != null) {
                if (sVerbose) Slog.v(TAG, "adding footer");
                items.add(new ViewItem(null, null, false, null, footer));
            }


            mAdapter = new ItemsAdapter(items);
            mAdapter = new ItemsAdapter(items);


@@ -311,11 +322,6 @@ final class FillUi {
            mListView.setVisibility(View.VISIBLE);
            mListView.setVisibility(View.VISIBLE);
            mListView.setOnItemClickListener((adapter, view, position, id) -> {
            mListView.setOnItemClickListener((adapter, view, position, id) -> {
                final ViewItem vi = mAdapter.getItem(position);
                final ViewItem vi = mAdapter.getItem(position);
                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);
                mCallback.onDatasetPicked(vi.dataset);
            });
            });


@@ -460,6 +466,13 @@ final class FillUi {
            changed = true;
            changed = true;
            mContentWidth = maxSize.x;
            mContentWidth = maxSize.x;
        }
        }

        if (mHeader != null) {
            mHeader.measure(widthMeasureSpec, heightMeasureSpec);
            changed |= updateWidth(mHeader, maxSize);
            changed |= updateHeight(mHeader, maxSize);
        }

        for (int i = 0; i < itemCount; i++) {
        for (int i = 0; i < itemCount; i++) {
            final View view = mAdapter.getItem(i).view;
            final View view = mAdapter.getItem(i).view;
            view.measure(widthMeasureSpec, heightMeasureSpec);
            view.measure(widthMeasureSpec, heightMeasureSpec);
@@ -473,23 +486,40 @@ final class FillUi {
                    break;
                    break;
                }
                }
            } else {
            } else {
                changed |= updateWidth(view, maxSize);
                if (i < sVisibleDatasetsMaxCount) {
                    changed |= updateHeight(view, maxSize);
                }
            }
        }

        if (mFooter != null) {
            mFooter.measure(widthMeasureSpec, heightMeasureSpec);
            changed |= updateWidth(mFooter, maxSize);
            changed |= updateHeight(mFooter, maxSize);
        }
        return changed;
    }

    private boolean updateWidth(View view, Point maxSize) {
        boolean changed = false;
        final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x);
        final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x);
        final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth);
        final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth);
        if (newContentWidth != mContentWidth) {
        if (newContentWidth != mContentWidth) {
            mContentWidth = newContentWidth;
            mContentWidth = newContentWidth;
            changed = true;
            changed = true;
        }
        }
                // Update the width to fit only the first items up to max count
        return changed;
                if (i < sVisibleDatasetsMaxCount) {
    }

    private boolean updateHeight(View view, Point maxSize) {
        boolean changed = false;
        final int clampedMeasuredHeight = Math.min(view.getMeasuredHeight(), maxSize.y);
        final int clampedMeasuredHeight = Math.min(view.getMeasuredHeight(), maxSize.y);
        final int newContentHeight = mContentHeight + clampedMeasuredHeight;
        final int newContentHeight = mContentHeight + clampedMeasuredHeight;
        if (newContentHeight != mContentHeight) {
        if (newContentHeight != mContentHeight) {
            mContentHeight = newContentHeight;
            mContentHeight = newContentHeight;
            changed = true;
            changed = true;
        }
        }
                }
            }
        }
        return changed;
        return changed;
    }
    }


@@ -501,7 +531,7 @@ final class FillUi {


    private static void resolveMaxWindowSize(Context context, Point outPoint) {
    private static void resolveMaxWindowSize(Context context, Point outPoint) {
        context.getDisplay().getSize(outPoint);
        context.getDisplay().getSize(outPoint);
        TypedValue typedValue = sTempTypedValue;
        final TypedValue typedValue = sTempTypedValue;
        context.getTheme().resolveAttribute(R.attr.autofillDatasetPickerMaxWidth,
        context.getTheme().resolveAttribute(R.attr.autofillDatasetPickerMaxWidth,
                typedValue, true);
                typedValue, true);
        outPoint.x = (int) typedValue.getFraction(outPoint.x, outPoint.x);
        outPoint.x = (int) typedValue.getFraction(outPoint.x, outPoint.x);
@@ -688,17 +718,27 @@ final class FillUi {
    public void dump(PrintWriter pw, String prefix) {
    public void dump(PrintWriter pw, String prefix) {
        pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
        pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
        pw.print(prefix); pw.print("mFullScreen: "); pw.println(mFullScreen);
        pw.print(prefix); pw.print("mFullScreen: "); pw.println(mFullScreen);
        if (mHeader != null) {
            pw.print(prefix); pw.print("mHeader: "); pw.println(mHeader);
        }
        if (mListView != null) {
            pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
            pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
        }
        if (mFooter != null) {
            pw.print(prefix); pw.print("mFooter: "); pw.println(mFooter);
        }
        if (mAdapter != null) {
            pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter);
            pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter);
        }
        if (mFilterText != null) {
            pw.print(prefix); pw.print("mFilterText: ");
            pw.print(prefix); pw.print("mFilterText: ");
            Helper.printlnRedactedText(pw, mFilterText);
            Helper.printlnRedactedText(pw, mFilterText);
        }
        pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
        pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
        pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
        pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
        pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
        pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
        if (mWindow != null) {
            pw.print(prefix); pw.print("mWindow: ");
            pw.print(prefix); pw.print("mWindow: ");
        if (mWindow == null) {
            pw.println("N/A");
        } else {
            final String prefix2 = prefix + "  ";
            final String prefix2 = prefix + "  ";
            pw.println();
            pw.println();
            pw.print(prefix2); pw.print("showing: "); pw.println(mWindow.mShowing);
            pw.print(prefix2); pw.print("showing: "); pw.println(mWindow.mShowing);