root/ui/android/java/src/org/chromium/ui/autofill/AutofillPopup.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. requestHide
  2. suggestionSelected
  3. show
  4. setAnchorRect
  5. show
  6. dismiss
  7. hide
  8. getDesiredWidth
  9. onItemClick

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.autofill;

import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.widget.AdapterView;
import android.widget.ListPopupWindow;
import android.widget.TextView;

import org.chromium.ui.R;
import org.chromium.ui.base.ViewAndroidDelegate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * The Autofill suggestion popup that lists relevant suggestions.
 */
public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {

    /**
     * Constants defining types of Autofill suggestion entries.
     * Has to be kept in sync with enum in WebAutofillClient.h
     *
     * Not supported: MenuItemIDWarningMessage, MenuItemIDClearForm, and
     * MenuItemIDAutofillOptions.
     */
    private static final int ITEM_ID_AUTOCOMPLETE_ENTRY = 0;
    private static final int ITEM_ID_PASSWORD_ENTRY = -2;
    private static final int ITEM_ID_SEPARATOR_ENTRY = -3;
    private static final int ITEM_ID_DATA_LIST_ENTRY = -6;

    private static final int TEXT_PADDING_DP = 30;

    private final AutofillPopupDelegate mAutofillCallback;
    private final Context mContext;
    private final ViewAndroidDelegate mViewAndroidDelegate;
    private View mAnchorView;
    private float mAnchorWidth;
    private float mAnchorHeight;
    private float mAnchorX;
    private float mAnchorY;
    private Paint mLabelViewPaint;
    private Paint mSublabelViewPaint;
    private OnLayoutChangeListener mLayoutChangeListener;
    private List<AutofillSuggestion> mSuggestions;


    /**
     * An interface to handle the touch interaction with an AutofillPopup object.
     */
    public interface AutofillPopupDelegate {
        /**
         * Requests the controller to hide AutofillPopup.
         */
        public void requestHide();

        /**
         * Handles the selection of an Autofill suggestion from an AutofillPopup.
         * @param listIndex The index of the selected Autofill suggestion.
         */
        public void suggestionSelected(int listIndex);
    }

    /**
     * Creates an AutofillWindow with specified parameters.
     * @param context Application context.
     * @param viewAndroidDelegate View delegate used to add and remove views.
     * @param autofillCallback A object that handles the calls to the native AutofillPopupView.
     */
    public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
            AutofillPopupDelegate autofillCallback) {
        super(context, null, 0, R.style.AutofillPopupWindow);
        mContext = context;
        mViewAndroidDelegate = viewAndroidDelegate;
        mAutofillCallback = autofillCallback;

        setOnItemClickListener(this);

        mAnchorView = mViewAndroidDelegate.acquireAnchorView();
        mAnchorView.setId(R.id.autofill_popup_window);
        mAnchorView.setTag(this);

        mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
                mAnchorHeight);

        mLayoutChangeListener = new OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom,
                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
                if (v == mAnchorView) AutofillPopup.this.show();
            }
        };

        mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
        setAnchorView(mAnchorView);
    }

    @Override
    public void show() {
        // An ugly hack to keep the popup from expanding on top of the keyboard.
        setInputMethodMode(INPUT_METHOD_NEEDED);
        super.show();
        getListView().setDividerHeight(0);
    }

    /**
     * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
     * itself.
     * @param x X coordinate of the top left corner of the anchor view.
     * @param y Y coordinate of the top left corner of the anchor view.
     * @param width The width of the anchor view.
     * @param height The height of the anchor view.
     */
    public void setAnchorRect(float x, float y, float width, float height) {
        mAnchorWidth = width;
        mAnchorHeight = height;
        mAnchorX = x;
        mAnchorY = y;
        if (mAnchorView != null) {
            mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
                    mAnchorWidth, mAnchorHeight);
        }
    }

    /**
     * Sets the Autofill suggestions to display in the popup and shows the popup.
     * @param suggestions Autofill suggestion data.
     */
    public void show(AutofillSuggestion[] suggestions) {
        mSuggestions = new ArrayList<AutofillSuggestion>(Arrays.asList(suggestions));
        // Remove the AutofillSuggestions with IDs that are not supported by Android
        ArrayList<AutofillSuggestion> cleanedData = new ArrayList<AutofillSuggestion>();
        HashSet<Integer> separators = new HashSet<Integer>();
        for (int i = 0; i < suggestions.length; i++) {
            int itemId = suggestions[i].mUniqueId;
            if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY ||
                    itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) {
                cleanedData.add(suggestions[i]);
            } else if (itemId == ITEM_ID_SEPARATOR_ENTRY) {
                separators.add(cleanedData.size());
            }
        }
        setAdapter(new AutofillListAdapter(mContext, cleanedData, separators));
        // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup.
        mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth);
        mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
                mAnchorHeight);
    }

    /**
     * Overrides the default dismiss behavior to request the controller to dismiss the view.
     */
    @Override
    public void dismiss() {
        mAutofillCallback.requestHide();
    }

    /**
     * Hides the popup and removes the anchor view from the ContainerView.
     */
    public void hide() {
        super.dismiss();
        mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
        mAnchorView.setTag(null);
        mViewAndroidDelegate.releaseAnchorView(mAnchorView);
    }

    /**
     * Get desired popup window width by calculating the maximum text length from Autofill data.
     * @param data Autofill suggestion data.
     * @return The popup window width in DIP.
     */
    private float getDesiredWidth(ArrayList<AutofillSuggestion> data) {
        if (mLabelViewPaint == null || mSublabelViewPaint == null) {
            LayoutInflater inflater =
                    (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(R.layout.autofill_text, null);
            TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
            mLabelViewPaint = labelView.getPaint();
            TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
            mSublabelViewPaint = sublabelView.getPaint();
        }

        float maxTextWidth = 0;
        Rect bounds = new Rect();
        for (int i = 0; i < data.size(); ++i) {
            bounds.setEmpty();
            String label = data.get(i).mLabel;
            if (!TextUtils.isEmpty(label)) {
                mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
            }
            float labelWidth = bounds.width();

            bounds.setEmpty();
            String sublabel = data.get(i).mSublabel;
            if (!TextUtils.isEmpty(sublabel)) {
                mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
            }

            float localMax = Math.max(labelWidth, bounds.width());
            maxTextWidth = Math.max(maxTextWidth, localMax);
        }
        // Scale it down to make it unscaled by screen density.
        maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
        // Adding padding.
        return maxTextWidth + TEXT_PADDING_DP;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        AutofillListAdapter adapter = (AutofillListAdapter) parent.getAdapter();
        AutofillSuggestion selectedSuggestion = adapter.getItem(position);
        int listIndex = mSuggestions.indexOf(selectedSuggestion);
        assert listIndex > -1;
        mAutofillCallback.suggestionSelected(listIndex);
    }
}

/* [<][>][^][v][top][bottom][index][help] */