| Index: chrome/android/java/src/org/chromium/chrome/browser/firstrun/AccountFirstRunView.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/AccountFirstRunView.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/AccountFirstRunView.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..04753c7f29b6fccf4cb4a81bff4342dc2fc66eda
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/AccountFirstRunView.java
|
| @@ -0,0 +1,503 @@
|
| +// Copyright 2015 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.firstrun;
|
| +
|
| +import android.content.Context;
|
| +import android.graphics.Bitmap;
|
| +import android.graphics.Color;
|
| +import android.text.TextUtils;
|
| +import android.util.AttributeSet;
|
| +import android.view.Gravity;
|
| +import android.view.View;
|
| +import android.widget.AdapterView;
|
| +import android.widget.ArrayAdapter;
|
| +import android.widget.Button;
|
| +import android.widget.FrameLayout;
|
| +import android.widget.ImageView;
|
| +import android.widget.LinearLayout;
|
| +import android.widget.Spinner;
|
| +import android.widget.TextView;
|
| +
|
| +import org.chromium.base.ApiCompatibilityUtils;
|
| +import org.chromium.chrome.R;
|
| +import org.chromium.chrome.browser.firstrun.ImageCarousel.ImageCarouselPositionChangeListener;
|
| +import org.chromium.chrome.browser.profiles.ProfileDownloader;
|
| +import org.chromium.chrome.browser.signin.SigninManager;
|
| +import org.chromium.chrome.browser.widget.ButtonCompat;
|
| +import org.chromium.sync.signin.AccountManagerHelper;
|
| +
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * This view allows the user to select an account to log in to, add an account,
|
| + * cancel account selection, etc. Users of this class should
|
| + * {@link AccountFirstRunView#setListener(Listener)} after the view has been
|
| + * inflated.
|
| + */
|
| +public class AccountFirstRunView extends FrameLayout
|
| + implements ImageCarouselPositionChangeListener, ProfileDownloader.Observer {
|
| +
|
| + /**
|
| + * Callbacks for various account selection events.
|
| + */
|
| + public interface Listener {
|
| + /**
|
| + * The user selected an account.
|
| + * @param accountName The name of the account
|
| + */
|
| + public void onAccountSelectionConfirmed(String accountName);
|
| +
|
| + /**
|
| + * The user canceled account selection.
|
| + */
|
| + public void onAccountSelectionCanceled();
|
| +
|
| + /**
|
| + * The user wants to make a new account.
|
| + */
|
| + public void onNewAccount();
|
| +
|
| + /**
|
| + * The user has been signed in and pressed 'Done' button.
|
| + * @param accountName The name of the account
|
| + */
|
| + public void onSigningInCompleted(String accountName);
|
| +
|
| + /**
|
| + * The user has signed in and pressed 'Settings' button.
|
| + * @param accountName The name of the account
|
| + */
|
| + public void onSettingsButtonClicked(String accountName);
|
| +
|
| + /**
|
| + * Failed to set the forced account because it wasn't found.
|
| + * @param forcedAccountName The name of the forced-sign-in account
|
| + */
|
| + public void onFailedToSetForcedAccount(String forcedAccountName);
|
| + }
|
| +
|
| + private class SpinnerOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
|
| + @Override
|
| + public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
|
| + String accountName = parent.getItemAtPosition(pos).toString();
|
| + if (accountName.equals(mAddAnotherAccount)) {
|
| + // Don't allow "add account" to remain selected. http://crbug.com/421052
|
| + int oldPosition = mArrayAdapter.getPosition(mAccountName);
|
| + if (oldPosition == -1) oldPosition = 0;
|
| + mSpinner.setSelection(oldPosition, false);
|
| +
|
| + mListener.onNewAccount();
|
| + } else {
|
| + mAccountName = accountName;
|
| + if (!mPositionSetProgrammatically) mImageCarousel.scrollTo(pos, false, false);
|
| + mPositionSetProgrammatically = false;
|
| + }
|
| + }
|
| + @Override
|
| + public void onNothingSelected(AdapterView<?> parent) {
|
| + mAccountName = parent.getItemAtPosition(0).toString();
|
| + }
|
| + }
|
| +
|
| + private static final int EXPERIMENT_TITLE_VARIANT_MASK = 1;
|
| + private static final int EXPERIMENT_SUMMARY_VARIANT_MASK = 2;
|
| + private static final int EXPERIMENT_LAYOUT_VARIANT_MASK = 4;
|
| + private static final int EXPERIMENT_MAX_VALUE = 7;
|
| +
|
| + private AccountManagerHelper mAccountManagerHelper;
|
| + private List<String> mAccountNames;
|
| + private ArrayAdapter<CharSequence> mArrayAdapter;
|
| + private ImageCarousel mImageCarousel;
|
| + private Button mPositiveButton;
|
| + private Button mNegativeButton;
|
| + private TextView mDescriptionText;
|
| + private Listener mListener;
|
| + private Spinner mSpinner;
|
| + private String mForcedAccountName;
|
| + private String mAccountName;
|
| + private String mAddAnotherAccount;
|
| + private ProfileDataCache mProfileData;
|
| + private boolean mSignedIn;
|
| + private boolean mPositionSetProgrammatically;
|
| + private int mDescriptionTextId;
|
| + private boolean mIsChildAccount;
|
| + private boolean mHorizontalModeEnabled = true;
|
| +
|
| + public AccountFirstRunView(Context context, AttributeSet attrs) {
|
| + super(context, attrs);
|
| + }
|
| +
|
| + /**
|
| + * Initializes this view with profile images and full names.
|
| + * @param profileData ProfileDataCache that will be used to call to retrieve user account info.
|
| + */
|
| + public void init(ProfileDataCache profileData) {
|
| + setProfileDataCache(profileData);
|
| + }
|
| +
|
| + /**
|
| + * Sets the profile data cache.
|
| + * @param profileData ProfileDataCache that will be used to call to retrieve user account info.
|
| + */
|
| + public void setProfileDataCache(ProfileDataCache profileData) {
|
| + mProfileData = profileData;
|
| + mProfileData.setObserver(this);
|
| + updateProfileImages();
|
| + }
|
| +
|
| + @Override
|
| + protected void onFinishInflate() {
|
| + super.onFinishInflate();
|
| +
|
| + mImageCarousel = (ImageCarousel) findViewById(R.id.image_slider);
|
| + mImageCarousel.setListener(this);
|
| +
|
| + mPositiveButton = (Button) findViewById(R.id.positive_button);
|
| + mNegativeButton = (Button) findViewById(R.id.negative_button);
|
| + mNegativeButton.setOnClickListener(new OnClickListener() {
|
| + @Override
|
| + public void onClick(View v) {
|
| + setButtonsEnabled(false);
|
| + mListener.onAccountSelectionCanceled();
|
| + }
|
| + });
|
| +
|
| + mDescriptionText = (TextView) findViewById(R.id.description);
|
| + mDescriptionTextId = R.string.fre_account_choice_description;
|
| +
|
| + mAddAnotherAccount = getResources().getString(R.string.fre_add_account);
|
| +
|
| + mSpinner = (Spinner) findViewById(R.id.google_accounts_spinner);
|
| + mArrayAdapter = new ArrayAdapter<CharSequence>(
|
| + getContext().getApplicationContext(), R.layout.fre_spinner_text);
|
| +
|
| + updateAccounts();
|
| +
|
| + mArrayAdapter.setDropDownViewResource(R.layout.fre_spinner_dropdown);
|
| + mSpinner.setAdapter(mArrayAdapter);
|
| + mSpinner.setOnItemSelectedListener(new SpinnerOnItemSelectedListener());
|
| + }
|
| +
|
| + @Override
|
| + protected void onAttachedToWindow() {
|
| + super.onAttachedToWindow();
|
| + updateAccounts();
|
| + }
|
| +
|
| + @Override
|
| + public void onWindowVisibilityChanged(int visibility) {
|
| + super.onWindowVisibilityChanged(visibility);
|
| + if (visibility == View.VISIBLE) {
|
| + updateAccounts();
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
| + // This assumes that view's layout_width is set to match_parent.
|
| + assert MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
|
| + int width = MeasureSpec.getSize(widthMeasureSpec);
|
| + int height = MeasureSpec.getSize(heightMeasureSpec);
|
| + LinearLayout content = (LinearLayout) findViewById(R.id.fre_content);
|
| + int paddingStart = 0;
|
| + if (mHorizontalModeEnabled
|
| + && width >= 2 * getResources().getDimension(R.dimen.fre_image_carousel_width)
|
| + && width > height) {
|
| + content.setOrientation(LinearLayout.HORIZONTAL);
|
| + paddingStart = getResources().getDimensionPixelSize(R.dimen.fre_margin);
|
| + } else {
|
| + content.setOrientation(LinearLayout.VERTICAL);
|
| + }
|
| + ApiCompatibilityUtils.setPaddingRelative(content,
|
| + paddingStart,
|
| + content.getPaddingTop(),
|
| + ApiCompatibilityUtils.getPaddingEnd(content),
|
| + content.getPaddingBottom());
|
| + super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
| + }
|
| +
|
| + /**
|
| + * Changes the visuals slightly for when this view appears in the recent tabs page instead of
|
| + * in first run. For example, the title text is changed as well as the button style.
|
| + */
|
| + public void configureForRecentTabsPage() {
|
| + mHorizontalModeEnabled = false;
|
| +
|
| + setBackgroundResource(R.color.ntp_bg);
|
| + TextView title = (TextView) findViewById(R.id.title);
|
| + title.setText(R.string.sign_in_to_chrome);
|
| +
|
| + // Remove the border above the button, swap in a new button with a blue material background,
|
| + // and center the button.
|
| + View buttonBarSeparator = findViewById(R.id.button_bar_separator);
|
| + buttonBarSeparator.setVisibility(View.GONE);
|
| +
|
| + LinearLayout buttonContainer = (LinearLayout) findViewById(R.id.button_bar);
|
| + buttonContainer.setGravity(Gravity.CENTER_HORIZONTAL);
|
| + setPadding(0, 0, 0, getResources().getDimensionPixelOffset(
|
| + R.dimen.sign_in_promo_padding_bottom));
|
| +
|
| + ButtonCompat positiveButton = new ButtonCompat(getContext(),
|
| + getResources().getColor(R.color.light_active_color));
|
| + positiveButton.setTextColor(Color.WHITE);
|
| + positiveButton.setLayoutParams(new LinearLayout.LayoutParams(
|
| + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
| +
|
| + buttonContainer.removeView(mPositiveButton);
|
| + buttonContainer.addView(positiveButton);
|
| + mPositiveButton = positiveButton;
|
| + }
|
| +
|
| + /**
|
| + * Changes the visuals slightly for when this view is shown in a subsequent run after user adds
|
| + * a Google account to the device.
|
| + */
|
| + public void configureForAddAccountPromo() {
|
| + int experimentGroup = SigninManager.getAndroidSigninPromoExperimentGroup();
|
| + assert experimentGroup >= 0 && experimentGroup <= EXPERIMENT_MAX_VALUE;
|
| +
|
| + TextView title = (TextView) findViewById(R.id.title);
|
| + if ((experimentGroup & EXPERIMENT_TITLE_VARIANT_MASK) != 0) {
|
| + title.setText(R.string.make_chrome_yours);
|
| + }
|
| +
|
| + mDescriptionTextId = (experimentGroup & EXPERIMENT_SUMMARY_VARIANT_MASK) != 0
|
| + ? R.string.sign_in_to_chrome_summary_variant : R.string.sign_in_to_chrome_summary;
|
| +
|
| + if ((experimentGroup & EXPERIMENT_LAYOUT_VARIANT_MASK) != 0) {
|
| + mImageCarousel.setVisibility(GONE);
|
| +
|
| + ImageView illustrationView = new ImageView(getContext());
|
| + illustrationView.setImageResource(R.drawable.signin_promo_illustration);
|
| + illustrationView.setBackgroundColor(getResources().getColor(
|
| + R.color.illustration_background_color));
|
| +
|
| + LinearLayout linearLayout = (LinearLayout) findViewById(R.id.fre_account_linear_layout);
|
| + linearLayout.addView(illustrationView, 0);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Enable or disable UI elements so the user can't select an account, cancel, etc.
|
| + *
|
| + * @param enabled The state to change to.
|
| + */
|
| + public void setButtonsEnabled(boolean enabled) {
|
| + mPositiveButton.setEnabled(enabled);
|
| + mNegativeButton.setEnabled(enabled);
|
| + }
|
| +
|
| + /**
|
| + * Set the account selection event listener. See {@link Listener}
|
| + *
|
| + * @param listener The listener.
|
| + */
|
| + public void setListener(Listener listener) {
|
| + mListener = listener;
|
| + }
|
| +
|
| + /**
|
| + * Tell the view whether or not the user can cancel account selection. In
|
| + * wizards, it makes sense to allow the user to skip account selection.
|
| + * However, in other settings-type contexts it does not make sense to allow
|
| + * this.
|
| + *
|
| + * @param canCancel Whether or not account selection can be canceled.
|
| + */
|
| + public void setCanCancel(boolean canCancel) {
|
| + mNegativeButton.setVisibility(canCancel ? View.VISIBLE : View.GONE);
|
| + mPositiveButton.setGravity(
|
| + canCancel ? Gravity.END | Gravity.CENTER_VERTICAL : Gravity.CENTER);
|
| + }
|
| +
|
| + /**
|
| + * Refresh the list of available system account.
|
| + */
|
| + private void updateAccounts() {
|
| + if (mSignedIn) return;
|
| + setButtonsEnabled(true);
|
| +
|
| + mAccountManagerHelper = AccountManagerHelper.get(getContext().getApplicationContext());
|
| +
|
| + List<String> oldAccountNames = mAccountNames;
|
| + mAccountNames = mAccountManagerHelper.getGoogleAccountNames();
|
| + int accountToSelect = 0;
|
| + if (mForcedAccountName != null) {
|
| + accountToSelect = mAccountNames.indexOf(mForcedAccountName);
|
| + if (accountToSelect < 0) {
|
| + mListener.onFailedToSetForcedAccount(mForcedAccountName);
|
| + return;
|
| + }
|
| + } else {
|
| + accountToSelect = getIndexOfNewElement(
|
| + oldAccountNames, mAccountNames, mSpinner.getSelectedItemPosition());
|
| + }
|
| +
|
| + mArrayAdapter.clear();
|
| + if (!mAccountNames.isEmpty()) {
|
| + mSpinner.setVisibility(View.VISIBLE);
|
| + mArrayAdapter.addAll(mAccountNames);
|
| + mArrayAdapter.add(mAddAnotherAccount);
|
| + mPositiveButton.setText(R.string.choose_account_sign_in);
|
| + mPositiveButton.setOnClickListener(new OnClickListener() {
|
| + @Override
|
| + public void onClick(View v) {
|
| + mListener.onAccountSelectionConfirmed(mAccountName);
|
| + }
|
| + });
|
| + mDescriptionText.setText(mDescriptionTextId);
|
| + } else {
|
| + mSpinner.setVisibility(View.GONE);
|
| + mArrayAdapter.add(mAddAnotherAccount);
|
| + mPositiveButton.setText(R.string.fre_no_accounts);
|
| + mPositiveButton.setOnClickListener(new OnClickListener() {
|
| + @Override
|
| + public void onClick(View v) {
|
| + mListener.onNewAccount();
|
| + }
|
| + });
|
| + mDescriptionText.setText(R.string.fre_no_account_choice_description);
|
| + }
|
| +
|
| + if (mProfileData != null) mProfileData.update();
|
| + updateProfileImages();
|
| +
|
| + mSpinner.setSelection(accountToSelect);
|
| + mAccountName = mArrayAdapter.getItem(accountToSelect).toString();
|
| + mImageCarousel.scrollTo(accountToSelect, false, false);
|
| + }
|
| +
|
| + /**
|
| + * Attempt to select a new element that is in the new list, but not in the old list.
|
| + * If no such element exist and both the new and the old lists are the same then keep
|
| + * the selection. Otherwise select the first element.
|
| + * @param oldList Old list of user accounts.
|
| + * @param newList New list of user accounts.
|
| + * @param oldIndex Index of the selected account in the old list.
|
| + * @return The index of the new element, if it does not exist but lists are the same the
|
| + * return the old index, otherwise return 0.
|
| + */
|
| + private static int getIndexOfNewElement(
|
| + List<String> oldList, List<String> newList, int oldIndex) {
|
| + if (oldList == null || newList == null) return 0;
|
| + if (oldList.size() == newList.size() && oldList.containsAll(newList)) return oldIndex;
|
| + if (oldList.size() + 1 == newList.size()) {
|
| + for (int i = 0; i < newList.size(); i++) {
|
| + if (!oldList.contains(newList.get(i))) return i;
|
| + }
|
| + }
|
| + return 0;
|
| + }
|
| +
|
| + @Override
|
| + public void onProfileDownloaded(String accountId, String fullName, String givenName,
|
| + Bitmap bitmap) {
|
| + updateProfileImages();
|
| + }
|
| +
|
| + private void updateProfileImages() {
|
| + if (mProfileData == null) return;
|
| +
|
| + int count = mAccountNames.size();
|
| +
|
| + Bitmap[] images;
|
| + if (count == 0) {
|
| + images = new Bitmap[1];
|
| + images[0] = mProfileData.getImage(null);
|
| + } else {
|
| + images = new Bitmap[count];
|
| + for (int i = 0; i < count; ++i) {
|
| + images[i] = mProfileData.getImage(mAccountNames.get(i));
|
| + }
|
| + }
|
| +
|
| + mImageCarousel.setImages(images);
|
| + updateProfileName();
|
| + }
|
| +
|
| + private void updateProfileName() {
|
| + if (!mSignedIn) return;
|
| +
|
| + String name = null;
|
| + if (mIsChildAccount) name = mProfileData.getGivenName(mAccountName);
|
| + if (name == null) name = mProfileData.getFullName(mAccountName);
|
| + if (name == null) name = mAccountName;
|
| + String text = String.format(getResources().getString(R.string.fre_hi_name), name);
|
| + ((TextView) findViewById(R.id.title)).setText(text);
|
| + }
|
| +
|
| + /**
|
| + * Updates the view to show that sign in has completed.
|
| + */
|
| + public void switchToSignedMode() {
|
| + mSignedIn = true;
|
| + updateProfileName();
|
| +
|
| + mSpinner.setEnabled(false);
|
| + ApiCompatibilityUtils.setBackgroundForView(mSpinner, null);
|
| + mPositiveButton.setText(getResources().getText(R.string.fre_done));
|
| + mPositiveButton.setOnClickListener(new OnClickListener() {
|
| + @Override
|
| + public void onClick(View v) {
|
| + mListener.onSigningInCompleted(mAccountName);
|
| + }
|
| + });
|
| + mNegativeButton.setText(getResources().getText(R.string.fre_settings));
|
| + mNegativeButton.setOnClickListener(new OnClickListener() {
|
| + @Override
|
| + public void onClick(View v) {
|
| + mListener.onSettingsButtonClicked(mAccountName);
|
| + }
|
| + });
|
| + setButtonsEnabled(true);
|
| + String text = getResources().getString(R.string.fre_signed_in_description);
|
| + if (mIsChildAccount) {
|
| + text += "\n" + getResources().getString(
|
| + R.string.fre_signed_in_description_uca_addendum);
|
| + }
|
| + mDescriptionText.setText(text);
|
| + mImageCarousel.setVisibility(VISIBLE);
|
| + mImageCarousel.setSignedInMode();
|
| + }
|
| +
|
| + /**
|
| + * @param isChildAccount Whether this view is for a child account.
|
| + */
|
| + public void setIsChildAccount(boolean isChildAccount) {
|
| + mIsChildAccount = isChildAccount;
|
| + }
|
| +
|
| + /**
|
| + * Switches the view to "no choice, just a confirmation" forced-account mode.
|
| + * @param forcedAccountName An account that should be force-selected.
|
| + */
|
| + public void switchToForcedAccountMode(String forcedAccountName) {
|
| + mForcedAccountName = forcedAccountName;
|
| + updateAccounts();
|
| + assert TextUtils.equals(mAccountName, mForcedAccountName);
|
| + switchToSignedMode();
|
| + assert TextUtils.equals(mAccountName, mForcedAccountName);
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the view is in signed in mode.
|
| + */
|
| + public boolean isSignedIn() {
|
| + return mSignedIn;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the view is in "no choice, just a confirmation" forced-account mode.
|
| + */
|
| + public boolean isInForcedAccountMode() {
|
| + return mForcedAccountName != null;
|
| + }
|
| +
|
| + @Override
|
| + public void onPositionChanged(int i) {
|
| + mPositionSetProgrammatically = true;
|
| + mSpinner.setSelection(i);
|
| + }
|
| +}
|
|
|