// Copyright 2014 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.chrome.browser.autofill; import android.content.Context; import android.text.SpannableString; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; import org.chromium.chrome.R; import org.chromium.ui.text.NoUnderlineClickableSpan; import java.util.Arrays; import java.util.List; /** * The adapter that populates the list popup for password generation with data. If the constructor * parameter passwordDisplayed is true, then this adapter makes the popup display two items in the * list: (1) the password suggestion and (2) an explanation of the password generation feature. If * the passwordDisplayed parameter is false, then the adapter shows only the explanation item. */ public class PasswordGenerationAdapter extends BaseAdapter { private final Context mContext; private final Delegate mDelegate; private final List<Integer> mViewTypes; private final String mPassword; private final String mSuggestionTitle; private final String mExplanationText; private final int mExplanationTextLinkRangeStart; private final int mExplanationTextLinkRangeEnd; private final int mSuggestionMeasuredWidth; /** * UI shows a generated password suggestion. */ private static final int SUGGESTION = 0; /** * UI shows an explanation about storing passwords in Chrome. */ private static final int EXPLANATION = 1; /** * There're 2 types of views: SUGGESTION and EXPLANATION. */ private static final int VIEW_TYPE_COUNT = 2; /** * Handler for clicks on the "saved passwords" link. */ public interface Delegate { /** * Called when the user clicks the "saved passwords" link. */ public void onSavedPasswordsLinkClicked(); } /** * Builds the adapter to display views using data from delegate. * @param context Android context. * @param delegate The handler for clicking on the "saved passwords" link. * @param passwordDisplayed Whether the auto-generated password should be suggested. * @param password The auto-generated password to suggest. * @param suggestionTitle The translated title of the suggestion part of the UI. * @param explanationText The translated text for the explanation part of the UI. * @param explanationTextLinkRangeStart The start of the range in the explanation text that * should be a link to the saved passwords. * @param explanationTextLinkRangeEnd The end of the range in the explanation text that should * be a link to the saved passwords. * @param anchorWidthInDp The width of the anchor to which the popup is attached. Used to size * the explanation view. */ public PasswordGenerationAdapter(Context context, Delegate delegate, boolean passwordDisplayed, String password, String suggestionTitle, String explanationText, int explanationTextLinkRangeStart, int explanationTextLinkRangeEnd, float anchorWidthInDp) { super(); mContext = context; mDelegate = delegate; mViewTypes = passwordDisplayed ? Arrays.asList(SUGGESTION, EXPLANATION) : Arrays.asList(EXPLANATION); mPassword = password; mSuggestionTitle = suggestionTitle; mExplanationText = explanationText; mExplanationTextLinkRangeStart = explanationTextLinkRangeStart; mExplanationTextLinkRangeEnd = explanationTextLinkRangeEnd; int horizontalMarginInPx = Math.round(mContext.getResources().getDimension( R.dimen.password_generation_horizontal_margin)); int anchorWidthInPx = Math.round(anchorWidthInDp * mContext.getResources().getDisplayMetrics().density); View suggestion = getViewForType(SUGGESTION).findViewById( R.id.password_generation_suggestion); suggestion.setMinimumWidth(anchorWidthInPx - 2 * horizontalMarginInPx); suggestion.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); mSuggestionMeasuredWidth = suggestion.getMeasuredWidth(); } /** * Used by list popup window to draw an element. * @param position The position of the element in the popup list. * @param convertView If not null, the element view where the data goes. * @param parent The list popup. * @return The view of the popup list element at the given position. */ @Override public View getView(int position, View convertView, ViewGroup parent) { return convertView != null ? convertView : getViewForType(mViewTypes.get(position)); } /** * Builds the view of this type. * @param type The type of view to build. * @return The view for this viewType. */ private View getViewForType(int type) { LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = null; switch (type) { case SUGGESTION: view = inflater.inflate(R.layout.password_generation_popup_suggestion, null); ((TextView) view.findViewById(R.id.password_generation_title)) .setText(mSuggestionTitle); ((TextView) view.findViewById(R.id.password_generation_password)) .setText(mPassword); break; case EXPLANATION: view = inflater.inflate(R.layout.password_generation_popup_explanation, null); TextView explanation = (TextView) view .findViewById(R.id.password_generation_explanation); SpannableString explanationSpan = new SpannableString(mExplanationText); explanationSpan.setSpan( new NoUnderlineClickableSpan() { @Override public void onClick(View view) { mDelegate.onSavedPasswordsLinkClicked(); } }, mExplanationTextLinkRangeStart, mExplanationTextLinkRangeEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE); explanation.setText(explanationSpan); explanation.setMovementMethod(LinkMovementMethod.getInstance()); explanation.setLayoutParams(new LayoutParams(mSuggestionMeasuredWidth, LayoutParams.WRAP_CONTENT)); break; default: assert false : "Unknown view type"; break; } return view; } /** * Returns the data item associated with this position in the data set. * @return Always null. */ @Override public Object getItem(int position) { return null; } /** * Returns the row ID for the data set item at this position. * @return Always position. */ @Override public long getItemId(int position) { return position; } /** * Used by the popup window to determine which view should be reused to render the list item at * this position. * @return Either SUGGESTION or EXPLANATION. */ @Override public int getItemViewType(int position) { return mViewTypes.get(position); } /** * Used by the popup window to determine how many different views should be reused to render the * popup. * @return Always 2. */ @Override public int getViewTypeCount() { return VIEW_TYPE_COUNT; } /** * Used by the popup window to determine how many items should be displayed in the list. * @return Either 1 or 2. */ @Override public int getCount() { return mViewTypes.size(); } /** * Used by list popup window to check if all of the elements are enabled. All password * generation popups have an explanation element, which is not selectable. Therefore, this * method always returns false: some of the items are disabled. * @return boolean Always false. */ @Override public boolean areAllItemsEnabled() { return false; } /** * Used by list popup window to check if the element at this position is enabled. Only the * suggestion element is enabled. * @return boolean True if the view at position is a suggestion. */ @Override public boolean isEnabled(int position) { return mViewTypes.get(position) == SUGGESTION; } }