Commit 60f51ca8 authored by aurimas's avatar aurimas Committed by Commit bot

Upstream Account chooser fragment of First Run.

BUG=428842
TBR=jbudorick@chromium.org

Review URL: https://codereview.chromium.org/954933004

Cr-Commit-Position: refs/heads/master@{#318280}
parent 3928ba71
...@@ -6,3 +6,4 @@ M D UuF: Unused public or protected field: org.chromium.chrome.browser.document. ...@@ -6,3 +6,4 @@ M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.
M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.originalIntent In PendingDocumentData.java M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.originalIntent In PendingDocumentData.java
M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.url In PendingDocumentData.java M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.url In PendingDocumentData.java
M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.requestId In PendingDocumentData.java M D UuF: Unused public or protected field: org.chromium.chrome.browser.document.PendingDocumentData.requestId In PendingDocumentData.java
M D BC: Unchecked/unconfirmed cast from android.view.View to org.chromium.chrome.browser.firstrun.AccountFirstRunView in org.chromium.chrome.browser.firstrun.AccountFirstRunFragment.onViewCreated(View, Bundle) At AccountFirstRunFragment.java
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<org.chromium.chrome.browser.firstrun.AccountFirstRunView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fre_account_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="57dp"
android:fillViewport="true"
android:scrollbarStyle="outsideOverlay" >
<LinearLayout
android:id="@+id/fre_account_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/fre_margin"
android:gravity="center"
android:lineSpacingMultiplier="1.4"
android:text="@string/fre_set_up_chrome"
android:textColor="@color/fre_title_color"
android:textSize="@dimen/fre_title_text_size" />
<LinearLayout
android:id="@+id/fre_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/fre_margin"
android:gravity="center_horizontal"
android:orientation="vertical" >
<org.chromium.chrome.browser.firstrun.ImageCarousel
android:id="@+id/image_slider"
android:layout_width="@dimen/fre_image_carousel_width"
android:layout_height="@dimen/fre_image_carousel_height"
android:layout_marginBottom="@dimen/fre_margin"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/fre_margin"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingEnd="24dp"
android:paddingStart="24dp" >
<Spinner
android:id="@+id/google_accounts_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/fre_margin"
android:padding="0dp"
android:contentDescription="@string/accessibility_fre_account_spinner"
android:textColor="@color/fre_text_color" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:lineSpacingMultiplier="1.4"
android:text="@string/fre_account_choice_description"
android:textColor="@color/fre_light_text_color"
android:textSize="@dimen/fre_normal_text_size" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<View android:id="@+id/button_bar_separator"
style="@style/ButtonBarTopDivider"
android:layout_gravity="bottom"
android:layout_marginBottom="56dp" />
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:orientation="horizontal" >
<Button
android:id="@+id/negative_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/listChoiceBackgroundIndicator"
android:gravity="start|center_vertical"
android:textDirection="locale"
android:padding="16dp"
android:text="@string/fre_skip_text"
android:textAllCaps="true"
android:textColor="@color/light_normal_color"
android:textSize="@dimen/fre_button_text_size" />
<Button
android:id="@+id/positive_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/listChoiceBackgroundIndicator"
android:gravity="end|center_vertical"
android:textDirection="locale"
android:padding="16dp"
android:text="@string/choose_account_sign_in"
android:textAllCaps="true"
android:textColor="@color/light_active_color"
android:textSize="@dimen/fre_button_text_size" />
</LinearLayout>
</org.chromium.chrome.browser.firstrun.AccountFirstRunView>
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/spinnerDropDownItemStyle"
android:singleLine="true"
android:ellipsize="end"
android:maxWidth="216dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/fre_text_color"
android:textSize="@dimen/fre_normal_text_size"
android:padding="@dimen/fre_button_padding" />
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fre_spinner_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="216dp"
android:layoutDirection="locale"
android:padding="@dimen/fre_button_padding"
android:paddingBottom="16dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="16dp"
android:textAlignment="viewStart"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/fre_text_color"
android:textSize="@dimen/fre_normal_text_size" />
...@@ -68,4 +68,14 @@ ...@@ -68,4 +68,14 @@
<color name="compositor_tab_title_bar_shadow">#AAFFFFFF</color> <color name="compositor_tab_title_bar_shadow">#AAFFFFFF</color>
<color name="compositor_tab_title_bar_shadow_incognito">#88000000</color> <color name="compositor_tab_title_bar_shadow_incognito">#88000000</color>
<!-- First Run Experience Colors -->
<color name="fre_text_color">#000000</color>
<color name="fre_light_text_color">#494949</color>
<color name="fre_title_color">#161616</color>
<!-- NTP Colors. Used on the bookmarks and recent tabs pages. -->
<color name="ntp_bg">#fff</color>
<!-- Signin promo screen colors -->
<color name="illustration_background_color">#4FC3F7</color>
</resources> </resources>
...@@ -105,4 +105,16 @@ ...@@ -105,4 +105,16 @@
<!-- Account Chooser infobar Dimensions --> <!-- Account Chooser infobar Dimensions -->
<dimen name="account_chooser_infobar_item_height">80dp</dimen> <dimen name="account_chooser_infobar_item_height">80dp</dimen>
<!-- First Run Experience and Welcome Page dimensions -->
<dimen name="fre_margin">24dp</dimen>
<dimen name="fre_title_text_size">24sp</dimen>
<dimen name="fre_button_text_size">14sp</dimen>
<dimen name="fre_button_padding">12dp</dimen>
<dimen name="fre_normal_text_size">14sp</dimen>
<dimen name="fre_image_carousel_width">260dp</dimen>
<dimen name="fre_image_carousel_height">130dp</dimen>
<dimen name="fre_checkmark_size">38dp</dimen>
<!-- Sign-in promo dimensions -->
<dimen name="sign_in_promo_padding_bottom">16dp</dimen>
</resources> </resources>
// 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.app.Fragment;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.R;
/**
* A {@link Fragment} meant to handle sync setup for the first run experience.
*/
public class AccountFirstRunFragment extends FirstRunPage {
// Per-page parameters:
public static final String FORCE_SIGNIN_ACCOUNT_TO = "ForceSigninAccountTo";
public static final String PRESELECT_BUT_ALLOW_TO_CHANGE = "PreselectButAllowToChange";
public static final String FORCE_SIGNIN_AND_DISABLE_NO_THANKS = "ForceSigninAndDisableNoThanks";
public static final String IS_CHILD_ACCOUNT = "IsChildAccount";
private AccountFirstRunView mView;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = (AccountFirstRunView) inflater.inflate(
R.layout.fre_choose_account, container, false);
return mView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
AccountFirstRunView firstRunView = (AccountFirstRunView) view;
firstRunView.setListener(new AccountFirstRunView.Listener() {
@Override
public void onAccountSelectionConfirmed(String accountName) {
mView.switchToSignedMode();
}
@Override
public void onAccountSelectionCanceled() {
getPageDelegate().refuseSignIn();
advanceToNextPage();
}
@Override
public void onNewAccount() {
getPageDelegate().openAccountAdder(AccountFirstRunFragment.this);
}
@Override
public void onSigningInCompleted(String accountName) {
getPageDelegate().acceptSignIn(accountName);
advanceToNextPage();
}
@Override
public void onSettingsButtonClicked(String accountName) {
getPageDelegate().acceptSignIn(accountName);
getPageDelegate().askToOpenSyncSettings();
advanceToNextPage();
}
@Override
public void onFailedToSetForcedAccount(String forcedAccountName) {
// Somehow the forced account disappeared while we were in the FRE.
// The user would have to go through the FRE again.
getPageDelegate().abortFirstRunExperience();
}
});
firstRunView.init(getPageDelegate().getProfileDataCache());
if (getProperties().getBoolean(FORCE_SIGNIN_AND_DISABLE_NO_THANKS)) {
firstRunView.setCanCancel(false);
}
firstRunView.setIsChildAccount(getProperties().getBoolean(IS_CHILD_ACCOUNT));
String forcedAccountName =
getProperties().getString(FORCE_SIGNIN_ACCOUNT_TO);
if (!TextUtils.isEmpty(forcedAccountName)) {
firstRunView.switchToForcedAccountMode(forcedAccountName);
}
}
@Override
public void onStart() {
super.onStart();
((AccountFirstRunView) getView()).setButtonsEnabled(true);
((AccountFirstRunView) getView()).setProfileDataCache(
getPageDelegate().getProfileDataCache());
getPageDelegate().onSigninDialogShown();
}
// FirstRunPage:
@Override
public boolean interceptBackPressed() {
if (!mView.isSignedIn()
|| (mView.isInForcedAccountMode()
&& !getProperties().getBoolean(PRESELECT_BUT_ALLOW_TO_CHANGE))) {
return super.interceptBackPressed();
}
if (mView.isInForcedAccountMode()
&& getProperties().getBoolean(PRESELECT_BUT_ALLOW_TO_CHANGE)) {
// Allow the user to choose the account or refuse to sign in,
// and re-create this fragment.
getProperties().remove(FORCE_SIGNIN_ACCOUNT_TO);
}
// Re-create the fragment if the user presses the back button when in signed in mode.
// The fragment is re-created in the normal (signed out) mode.
getPageDelegate().recreateCurrentPage();
return true;
}
}
// 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);
}
}
// 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.app.Fragment;
import android.content.Context;
import android.os.Bundle;
/**
* A first run page shown in the First Run ViewPager.
*/
public class FirstRunPage extends Fragment {
private Bundle mProperties = new Bundle();
// FRE properties:
private static final String LAST_PAGE_NUMBER = "LastPageNumber";
// Per-page properties:
private static final String THIS_PAGE_NUMBER = "ThisPageNumber";
/**
* Appends page-specific properties to the page instance bundle.
* @param props Instance properties.
* @param pageNumber This page number in the First Run sequence.
* @param lastPageNumber Last page number in the First Run sequence.
* @return Instance properties with the page-specific properties added.
*/
public static Bundle addProperties(Bundle props, int pageNumber, int lastPageNumber) {
props.putInt(THIS_PAGE_NUMBER, pageNumber);
return props;
}
/**
* @return Whether this page should be skipped on the FRE resume.
* @param appContext An application context.
*/
public boolean shouldSkipPageOnResume(Context appContext) {
return false;
}
// Fragment:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mProperties = savedInstanceState;
} else if (getArguments() != null) {
mProperties = getArguments();
} else {
mProperties = new Bundle();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putAll(mProperties);
}
/**
* @return Whether the back button press was handled by this page.
*/
public boolean interceptBackPressed() {
return false;
}
/**
* @return Passed arguments if any, or saved instance state if any, or an empty bundle.
*/
protected Bundle getProperties() {
return mProperties;
}
/**
* @return The interface to the host.
*/
protected FirstRunPageDelegate getPageDelegate() {
return (FirstRunPageDelegate) getActivity();
}
/**
* @return Whether this page is the last page of the FRE.
*/
protected boolean isLastPage() {
return getProperties().getInt(THIS_PAGE_NUMBER) == getProperties().getInt(LAST_PAGE_NUMBER);
}
/**
* Advances to the next FRE page.
*/
protected void advanceToNextPage() {
getPageDelegate().advanceToNextPage();
}
}
\ No newline at end of file
// 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.app.Fragment;
/**
* Defines the host interface for First Run Experience pages.
*/
public interface FirstRunPageDelegate {
/**
* @return A {@link ProfileDataCache} for Android user accounts.
*/
ProfileDataCache getProfileDataCache();
/**
* Advances the First Run Experience to the next page.
* Successfully finishes FRE if the current page is the last page.
*/
void advanceToNextPage();
/**
* Skips all intro pages.
* Successfully finishes FRE if there are no non-intro pages.
*/
void skipIntroPages();
/**
* Asks to re-instantiate the current page.
* Useful to restore the "clean" state of the UI elements.
*/
void recreateCurrentPage();
/**
* Unsuccessfully aborts the First Run Experience.
* This usually means that the application will be closed.
*/
void abortFirstRunExperience();
/**
* Successfully completes the First Run Experience.
* All results will be packaged and sent over to the main activity.
*/
void completeFirstRunExperience();
/**
* Notifies that the sign-in dialog is shown.
*/
void onSigninDialogShown();
/**
* Notifies that the user refused to sign in (e.g. "NO, THANKS").
*/
void refuseSignIn();
/**
* Notifies that the user accepted to be signed in.
* @param accountName An account to be signed in to.
*/
void acceptSignIn(String accountName);
/**
* Notifies that the user asked to show Sync Settings once the sign in
* process is complete.
*/
void askToOpenSyncSettings();
/**
* @return Whether the user has accepted Chrome Terms of Service.
*/
boolean didAcceptTermsOfService();
/**
* @return Whether the "upload crash dump" setting is set to "NEVER".
*/
boolean isNeverUploadCrashDump();
/**
* Notifies all interested parties that the user has accepted Chrome Terms of Service.
* @param allowCrashUpload True if the user allows to upload crash dumps and collect stats.
*/
void acceptTermsOfService(boolean allowCrashUpload);
/**
* Opens the Android account adder UI.
* @param fragment A fragment that needs the account adder UI.
*/
void openAccountAdder(Fragment fragment);
/**
* Opens Chrome Preferences on a given page.
* @param fragment A fragment that requested the service.
* @param pageClassName The preferences fragment class name.
*/
void openChromePreferencesPage(Fragment fragment, String pageClassName);
}
// 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.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.util.Property;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.chromium.chrome.R;
import java.util.Arrays;
/**
* Account chooser that displays profile images in a carousel and allows users to rotate it to
* select an account.
*
* Internally it is implemented using four ImageViews that get translated along the X axis based
* on the current carousel position.
*
* |'''''| |'''''| |'''''| |'''''| |'''''|
* |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''|
* |IM3| IM0 |IM1| -> |IM0| IM1 |IM2| -> |IM1| IM2 |IM3| -> |IM2| IM3 |IM0| -> |IM3| IM0 |IM1|
* |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,|
* |,,,,,| |,,,,,| |,,,,,| |,,,,,| |,,,,,|
*
* mPosition=0 mPosition=1 mPosition=2 mPosition=3 mPosition=4
*
* IM0 is mViews[0]
* IM1 is mViews[1]
* IM2 is mViews[2]
* IM3 is mViews[3]
*
* Each ImageView is displaying a profile image if there is one, however it is not necessarily true
* that IM0 is showing mImages[0] and IM1 is showing mImages[1], and so on. This changes when there
* are more than 4 accounts and ImageViews get reused for new accounts.
*/
public class ImageCarousel extends FrameLayout implements GestureDetector.OnGestureListener {
/**
* Constant used together image width to calculate how far should should each image move in
* x axis. This value was tweaked until images did not overlap with each other when scrolling.
*/
private static final float TRANSLATION_FACTOR = 0.64f;
/**
* Constant used together with carousel width to calculate how should fling velocity in x axis
* be scaled when changing ImageCarousel position. It was tweaked for flings to look natural.
*/
private static final float FLING_FACTOR = 20f * 0.92f / 2f;
/**
* Constant used together with carousel width to calculate how should scroll distance in x axis
* be scaled when changing ImageCarousel position. It was tweaked for image to follow user's
* finger when scrolling.
*/
private static final float SCROLL_FACTOR = 0.92f / 2f;
/**
* Listener to ImageCarousel center position changes.
*/
public interface ImageCarouselPositionChangeListener {
/**
* @param position The new center position of the ImageCarousel. It is a number in
* range [0, mImages.length).
*/
void onPositionChanged(int position);
}
private static final int SCROLL_ANIMATION_DURATION_MS = 200;
private static final int ACCOUNT_SIGNED_IN_ANIMATION_DURATION_MS = 200;
/**
* Number of ImageViews used in ImageCarousel.
*/
private static final int VIEW_COUNT = 4;
private static final int[] ORDER_OFFSETS = {2, 1, 3, 0};
private static final int[] POSITION_OFFSETS = {0, -1, 2, 1};
private static final int[] BITMAP_OFFSETS = {2, 1, -1, 0};
/**
* Property used to animate scrolling of the ImageCarousel.
*/
private static final Property<ImageCarousel, Float> POSITION_PROPERTY =
new Property<ImageCarousel, Float>(Float.class, "") {
@Override
public Float get(ImageCarousel object) {
return object.mPosition;
}
@Override
public void set(ImageCarousel object, Float value) {
object.setPosition(value);
}
};
/**
* Property used to animate the alpha value of the images that are currently on the left and
* the right of the center image.
*/
private static final Property<ImageCarousel, Float> BACKGROUND_IMAGE_ALPHA =
new Property<ImageCarousel, Float>(Float.class, "") {
@Override
public Float get(ImageCarousel object) {
return object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 1)].getAlpha();
}
@Override
public void set(ImageCarousel object, Float value) {
object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 1)].setAlpha(value);
object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 2)].setAlpha(value);
}
};
/**
* Gesture detector used to capture scrolls, flings and taps on the image carousel.
*/
private GestureDetector mGestureDetector;
/**
* Array that holds four ImageViews that are used to display images in the carousel.
*/
private ImageView[] mViews = new ImageView[VIEW_COUNT];
/**
* Images that shown in the image carousel.
*/
private Bitmap[] mImages;
private Animator mScrollAnimator;
private Animator mFadeInOutAnimator;
private float mPosition = 0f;
private ImageCarouselPositionChangeListener mListener;
private int mLastPosition = 0;
private boolean mNeedsPositionUpdates = true;
private int mCarouselWidth;
private int mImageWidth;
private float mScrollScalingFactor;
private float mFlingScalingFactor;
private float mTranslationFactor;
private boolean mScrollingDisabled;
private boolean mAccountSelected;
public ImageCarousel(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(getContext(), this);
}
/**
* Scrolls ImageCarousel to the closest whole position for the desired position.
* @param position Desired ImageCarousel position.
* @param decelerate Whether animation should be decelerating.
* @param needsPositionUpdates Whether this scroll should trigger position update calls to
* mListener.
*/
public void scrollTo(float position, boolean decelerate, boolean needsPositionUpdates) {
mNeedsPositionUpdates = needsPositionUpdates;
if (mScrollAnimator != null) mScrollAnimator.cancel();
position = Math.round(position);
mScrollAnimator = ObjectAnimator.ofFloat(this, POSITION_PROPERTY, mPosition, position);
mScrollAnimator.setDuration(SCROLL_ANIMATION_DURATION_MS);
if (decelerate) mScrollAnimator.setInterpolator(new DecelerateInterpolator());
mScrollAnimator.start();
}
/**
* @param listener Listener that should be notified on ImageCarousel center position changes.
*/
public void setListener(ImageCarouselPositionChangeListener listener) {
mListener = listener;
}
/**
* @param images Images that should be displayed in the ImageCarousel.
*/
public void setImages(Bitmap[] images) {
switch (images.length) {
case 0:
mImages = null;
mScrollingDisabled = true;
break;
case 1:
mScrollingDisabled = true;
mImages = Arrays.copyOf(images, images.length);
break;
default:
// Enable scrolling only if no account has already been selected.
mScrollingDisabled = mAccountSelected;
mImages = Arrays.copyOf(images, images.length);
break;
}
updateImageViews();
}
/**
* Sets the ImageCarousel to signed in mode that disables scrolling, animates away the
* background images, and displays a checkmark next to the account image that was chosen.
*/
public void setSignedInMode() {
mScrollingDisabled = true;
mAccountSelected = true;
setPosition(getCenterPosition());
ImageView checkmark = new ImageView(getContext());
checkmark.setImageResource(R.drawable.verify_checkmark);
setLayoutParamsForCheckmark(checkmark);
addView(checkmark);
if (mFadeInOutAnimator != null) mFadeInOutAnimator.cancel();
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(this, BACKGROUND_IMAGE_ALPHA, 0),
ObjectAnimator.ofFloat(checkmark, View.ALPHA, 0.0f, 1.0f));
mFadeInOutAnimator = animatorSet;
mFadeInOutAnimator.setDuration(ACCOUNT_SIGNED_IN_ANIMATION_DURATION_MS);
mFadeInOutAnimator.start();
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mImageWidth = getResources().getDimensionPixelSize(R.dimen.fre_image_carousel_height);
for (int i = 0; i < VIEW_COUNT; ++i) {
ImageView view = new ImageView(getContext());
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(mImageWidth, mImageWidth);
params.gravity = Gravity.CENTER;
view.setLayoutParams(params);
mViews[i] = view;
addView(view);
}
mCarouselWidth = getResources().getDimensionPixelSize(R.dimen.fre_image_carousel_width);
mScrollScalingFactor = SCROLL_FACTOR * mCarouselWidth;
mFlingScalingFactor = FLING_FACTOR * mCarouselWidth;
mTranslationFactor = TRANSLATION_FACTOR * mImageWidth;
setChildrenDrawingOrderEnabled(true);
setPosition(0f);
}
/**
* @return The index of the view that should be drawn on the given iteration.
*/
@Override
protected int getChildDrawingOrder(int childCount, int iteration) {
// Draw the views that are not our 4 ImagesViews in their normal order.
if (iteration >= VIEW_COUNT) return iteration;
// Draw image views in the correct z order based on the current position.
return (Math.round(mPosition) + ORDER_OFFSETS[iteration]) % VIEW_COUNT;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mScrollingDisabled) return false;
if (mGestureDetector.onTouchEvent(event)) return true;
if (event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL) {
scrollTo(mPosition, false, true);
}
return false;
}
// Implementation of GestureDetector.OnGestureListener
@Override
public boolean onDown(MotionEvent motionEvent) {
return true;
}
@Override
public void onShowPress(MotionEvent motionEvent) {}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
mNeedsPositionUpdates = true;
if (motionEvent.getX() < (mCarouselWidth - mImageWidth) / 2f) {
scrollTo(mPosition - 1, false, true);
return true;
} else if (motionEvent.getX() > (mCarouselWidth + mImageWidth) / 2f) {
scrollTo(mPosition + 1, false, true);
return true;
}
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// Once the user has started scrolling, prevent the parent view from handling touch events.
// This allows the ImageCarousel to be behave reasonably when nested inside a ScrollView.
getParent().requestDisallowInterceptTouchEvent(true);
mNeedsPositionUpdates = true;
setPosition(mPosition + distanceX / mScrollScalingFactor);
return true;
}
@Override
public void onLongPress(MotionEvent motionEvent) {}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mNeedsPositionUpdates = true;
scrollTo(mPosition - velocityX / mFlingScalingFactor, true, true);
return true;
}
// Internal methods
/**
* Updates the position, scale, alpha and image shown for all four ImageViews used by
* the ImageCarousel.
*/
private void updateImageViews() {
if (mImages == null) return;
for (int i = 0; i < VIEW_COUNT; i++) {
if (mAccountSelected && i != getCenterPosition()) continue;
ImageView image = mViews[i];
updateBitmap(i);
final float position = mPosition + POSITION_OFFSETS[i];
// X translation is a sin function with a period of 4 and with range
// [-mTranslationFactor, mTranslationFactor]
image.setTranslationX(
-mTranslationFactor * ((float) Math.sin(position * Math.PI / 2f)));
// scale is a cos function with a period of 4 and range [1/3, 1]
// scale is 1 when the image is in the front and 1/3 when the image is behind other
// images.
final float scale = (float) Math.cos(position * Math.PI / 2f) / 3f + 2f / 3f;
image.setScaleY(scale);
image.setScaleX(scale);
// alpha is a cos^2 function with a period of 2 and range [0, 1]
// alpha is 1 when the image is in the center in the front and 0 when it is in the back.
final float alpha = (float) Math.pow(Math.cos(position * Math.PI / 4f), 2);
image.setAlpha(alpha);
}
}
private void updateBitmap(int i) {
if (mImages.length == 1 && i < 3) return;
ImageView image = mViews[getChildDrawingOrder(VIEW_COUNT, i)];
image.setImageBitmap(mImages[
(mImages.length + Math.round(mPosition) + BITMAP_OFFSETS[i]) % mImages.length]);
}
private void setPosition(float position) {
if (mImages != null) {
mPosition = ((position % mImages.length) + mImages.length) % mImages.length;
}
int adjustedPosition = getCenterPosition();
if (adjustedPosition != mLastPosition) {
mLastPosition = adjustedPosition;
if (mListener != null && mNeedsPositionUpdates) {
mListener.onPositionChanged(adjustedPosition);
}
}
// Need to call invalidate() for getChildDrawingOrder() to be called since the image
// order has changed.
updateImageViews();
invalidate();
}
private int getCenterPosition() {
if (mImages == null) return 0;
return Math.round(mPosition) % mImages.length;
}
private void setLayoutParamsForCheckmark(View view) {
int size = getResources().getDimensionPixelSize(R.dimen.fre_checkmark_size);
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(size, size);
params.gravity = Gravity.CENTER;
view.setLayoutParams(params);
view.setTranslationX((mImageWidth - size) / 2f);
view.setTranslationY((mImageWidth - size) / 2f);
}
}
\ No newline at end of file
// 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.accounts.Account;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileDownloader;
import org.chromium.chrome.browser.profiles.ProfileDownloader.Observer;
import org.chromium.sync.signin.AccountManagerHelper;
import org.chromium.ui.gfx.DeviceDisplayInfo;
import java.util.HashMap;
/**
* Fetches and caches Google Account profile images and full names for the accounts on the device.
*/
public class ProfileDataCache implements Observer {
private static final int PROFILE_IMAGE_SIZE_DP = 136; // Max size of the user picture.
private static final int PROFILE_IMAGE_STROKE_DP = 3;
private static class CacheEntry {
public CacheEntry(Bitmap picture, String fullName, String givenName) {
this.picture = picture;
this.fullName = fullName;
this.givenName = givenName;
}
public Bitmap picture;
public String fullName;
public String givenName;
}
private final HashMap<String, CacheEntry> mCacheEntries = new HashMap<String, CacheEntry>();
private final Bitmap mPlaceholderImage;
private final int mImageSizePx;
private final int mImageStrokePx;
private final int mImageStrokeColor;
private Observer mObserver;
private final Activity mActivity;
private Profile mProfile;
public ProfileDataCache(Activity activity, Profile profile) {
ProfileDownloader.addObserver(this);
mActivity = activity;
mProfile = profile;
final DeviceDisplayInfo info = DeviceDisplayInfo.create(activity);
mImageSizePx = (int) Math.ceil(PROFILE_IMAGE_SIZE_DP * info.getDIPScale());
mImageStrokePx = (int) Math.ceil(PROFILE_IMAGE_STROKE_DP * info.getDIPScale());
mImageStrokeColor = Color.WHITE;
Bitmap placeHolder = BitmapFactory.decodeResource(mActivity.getResources(),
R.drawable.fre_placeholder);
mPlaceholderImage = getCroppedBitmap(placeHolder);
update();
}
/**
* Sets the profile to use for the fetcher and triggers the update.
* @param profile A profile to use.
*/
public void setProfile(Profile profile) {
mProfile = profile;
update();
}
/**
* Initiate fetching the user accounts data (images and the full name).
* Fetched data will be sent to observers of ProfileDownloader.
*/
public void update() {
if (mProfile == null) return;
Account[] accounts = AccountManagerHelper.get(mActivity).getGoogleAccounts();
for (int i = 0; i < accounts.length; i++) {
if (mCacheEntries.get(accounts[i].name) == null) {
ProfileDownloader.startFetchingAccountInfoFor(
mProfile, accounts[i].name, mImageSizePx);
}
}
}
/**
* @param accountId Google account ID for the image that is requested.
* @return Returns the profile image for a given Google account ID if it's in
* the cache, otherwise returns a placeholder image.
*/
public Bitmap getImage(String accountId) {
CacheEntry cacheEntry = mCacheEntries.get(accountId);
if (cacheEntry == null) return mPlaceholderImage;
return cacheEntry.picture;
}
/**
* @param accountId Google account ID for the full name that is requested.
* @return Returns the full name for a given Google account ID if it is
* the cache, otherwise returns null.
*/
public String getFullName(String accountId) {
CacheEntry cacheEntry = mCacheEntries.get(accountId);
if (cacheEntry == null) return null;
return cacheEntry.fullName;
}
/**
* @param accountId Google account ID for the full name that is requested.
* @return Returns the given name for a given Google account ID if it is in the cache, otherwise
* returns null.
*/
public String getGivenName(String accountId) {
CacheEntry cacheEntry = mCacheEntries.get(accountId);
if (cacheEntry == null) return null;
return cacheEntry.givenName;
}
public void onDestroy() {
ProfileDownloader.removeObserver(this);
mObserver = null;
}
@Override
public void onProfileDownloaded(String accountId, String fullName, String givenName,
Bitmap bitmap) {
bitmap = getCroppedBitmap(bitmap);
mCacheEntries.put(accountId, new CacheEntry(bitmap, fullName, givenName));
if (mObserver != null) mObserver.onProfileDownloaded(accountId, givenName, fullName,
bitmap);
}
private Bitmap getCroppedBitmap(Bitmap bitmap) {
Bitmap output = Bitmap.createBitmap(
bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(Color.WHITE);
final float radius = (bitmap.getWidth() - mImageStrokePx) / 2f;
canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, radius, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
paint.setColor(mImageStrokeColor);
paint.setStyle(Paint.Style.STROKE);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
paint.setStrokeWidth(mImageStrokePx);
canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, radius, paint);
return output;
}
/**
* @param observer Observer that should be notified when new profile images are available.
*/
public void setObserver(Observer observer) {
mObserver = observer;
}
}
...@@ -1241,6 +1241,53 @@ Drag from top to exit. ...@@ -1241,6 +1241,53 @@ Drag from top to exit.
Don't ask again for this card Don't ask again for this card
</message> </message>
<!-- First Run strings -->
<message name="IDS_FRE_SET_UP_CHROME" desc="Header text shown when the user is first setting up Chrome">
Set up Chrome
</message>
<message name="IDS_FRE_ACCOUNT_CHOICE_DESCRIPTION" desc="Text for account chooser page">
Select an account to get your tabs, bookmarks, history, and other settings on all your devices.
</message>
<message name="IDS_FRE_SKIP_TEXT" desc="Text for second page skip button">
No thanks
</message>
<message name="IDS_CHOOSE_ACCOUNT_SIGN_IN" desc="Sign in button for choose google account dialog [CHAR-LIMIT=20]">
Sign in
</message>
<message name="IDS_FRE_DONE" desc="Button to dismiss the sign-in screen once the user is done">
Done
</message>
<message name="IDS_FRE_SETTINGS" desc="Button that opens Chrome settings">
Settings
</message>
<message name="IDS_FRE_ADD_ACCOUNT" desc="Text for adding another Google Account">
Add account
</message>
<message name="IDS_MAKE_CHROME_YOURS" desc="Title for the signin promo that prompts the user to sign in to Chrome to have a personalized experirence. [CHAR-LIMIT=24]">
Make Chrome yours
</message>
<message name="IDS_FRE_NO_ACCOUNT_CHOICE_DESCRIPTION" desc="Text for account chooser page when there are no accounts on the device.">
Add an account to get your tabs, bookmarks, history, and other settings on all your devices.
</message>
<message name="IDS_SIGN_IN_TO_CHROME_SUMMARY_VARIANT" desc="A variant of summary for the signin promo; lists reasons to sign in to Chrome.">
Sign in to access all your web stuff from any device
</message>
<message name="IDS_FRE_NO_ACCOUNTS" desc="Text for spinner when there is no google accounts [CHAR-LIMIT=40]">
Add an account
</message>
<message name="IDS_FRE_HI_NAME" desc="Title welcoming the user by their name after they have signed in.">
Hi, <ph name="FULL_NAME">%1$s<ex>John Smith</ex></ph>
</message>
<message name="IDS_FRE_SIGNED_IN_DESCRIPTION" desc="Explanation of what gets synced after the user signed in">
Your tabs, bookmarks, history and other settings will be synced to your Google Account.
</message>
<message name="IDS_FRE_SIGNED_IN_DESCRIPTION_UCA_ADDENDUM" desc="Additional explanation for child accounts that synced settings are managed by parents.">
Your parents help manage these settings.
</message>
<message name="IDS_ACCESSIBILITY_FRE_ACCOUNT_SPINNER" desc="Content description for the first run account drop down spinner.">
Choose account
</message>
</messages> </messages>
</release> </release>
</grit> </grit>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment