Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/firstrun/AccountFirstRunView.java

Issue 1698043006: Created the dialog offering the user to merge their account data or keep it (Closed) Base URL: maybelle.lon.corp.google.com:/usr/local/google/code/clankium/src@sync_settings
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.firstrun;
6
7 import android.content.Context;
8 import android.graphics.Bitmap;
9 import android.graphics.drawable.Drawable;
10 import android.os.Bundle;
11 import android.text.TextPaint;
12 import android.text.TextUtils;
13 import android.text.method.LinkMovementMethod;
14 import android.text.style.ClickableSpan;
15 import android.util.AttributeSet;
16 import android.view.View;
17 import android.widget.AdapterView;
18 import android.widget.ArrayAdapter;
19 import android.widget.Button;
20 import android.widget.FrameLayout;
21 import android.widget.ImageView;
22 import android.widget.LinearLayout;
23 import android.widget.Spinner;
24 import android.widget.TextView;
25
26 import org.chromium.base.ApiCompatibilityUtils;
27 import org.chromium.chrome.R;
28 import org.chromium.chrome.browser.firstrun.ImageCarousel.ImageCarouselPositionC hangeListener;
29 import org.chromium.chrome.browser.profiles.ProfileDownloader;
30 import org.chromium.chrome.browser.signin.SigninManager;
31 import org.chromium.sync.signin.AccountManagerHelper;
32 import org.chromium.ui.text.SpanApplier;
33 import org.chromium.ui.text.SpanApplier.SpanInfo;
34
35 import java.util.List;
36
37 /**
38 * This view allows the user to select an account to log in to, add an account,
39 * cancel account selection, etc. Users of this class should
40 * {@link AccountFirstRunView#setListener(Listener)} after the view has been
41 * inflated.
42 */
43 public class AccountFirstRunView extends FrameLayout
44 implements ImageCarouselPositionChangeListener, ProfileDownloader.Observ er {
45
46 /**
47 * Callbacks for various account selection events.
48 */
49 public interface Listener {
50 /**
51 * The user canceled account selection.
52 */
53 public void onAccountSelectionCanceled();
54
55 /**
56 * The user wants to make a new account.
57 */
58 public void onNewAccount();
59
60 /**
61 * The user selected an account.
62 * This call will be followed by either {@link #onSettingsClicked} or
63 * {@link #onDoneClicked}.
64 * @param accountName The name of the account
65 */
66 public void onAccountSelected(String accountName);
67
68 /**
69 * The user has completed the dialog flow.
70 * This will only be called after {@link #onAccountSelected} and should exit the View.
71 */
72 public void onDoneClicked();
73
74 /**
75 * The user has selected to view sync settings.
76 * This will only be called after {@link #onAccountSelected} and should exit the View.
77 */
78 public void onSettingsClicked();
79
80 /**
81 * Failed to set the forced account because it wasn't found.
82 * @param forcedAccountName The name of the forced-sign-in account
83 */
84 public void onFailedToSetForcedAccount(String forcedAccountName);
85 }
86
87 private class SpinnerOnItemSelectedListener implements AdapterView.OnItemSel ectedListener {
88 @Override
89 public void onItemSelected(AdapterView<?> parent, View view, int pos, lo ng id) {
90 String accountName = parent.getItemAtPosition(pos).toString();
91 if (accountName.equals(mAddAnotherAccount)) {
92 // Don't allow "add account" to remain selected. http://crbug.co m/421052
93 int oldPosition = mArrayAdapter.getPosition(mAccountName);
94 if (oldPosition == -1) oldPosition = 0;
95 mSpinner.setSelection(oldPosition, false);
96
97 mListener.onNewAccount();
98 } else {
99 mAccountName = accountName;
100 if (!mPositionSetProgrammatically) mImageCarousel.scrollTo(pos, false, false);
101 mPositionSetProgrammatically = false;
102 }
103 }
104 @Override
105 public void onNothingSelected(AdapterView<?> parent) {
106 mAccountName = parent.getItemAtPosition(0).toString();
107 }
108 }
109
110 private static final String TAG = "AccountFirstRunView";
111
112 private static final int EXPERIMENT_TITLE_VARIANT_MASK = 1;
113 private static final int EXPERIMENT_SUMMARY_VARIANT_MASK = 2;
114 private static final int EXPERIMENT_LAYOUT_VARIANT_MASK = 4;
115 private static final int EXPERIMENT_MAX_VALUE = 7;
116
117 private static final String SETTINGS_LINK_OPEN = "<LINK1>";
118 private static final String SETTINGS_LINK_CLOSE = "</LINK1>";
119
120 private AccountManagerHelper mAccountManagerHelper;
121 private List<String> mAccountNames;
122 private ArrayAdapter<CharSequence> mArrayAdapter;
123 private ImageCarousel mImageCarousel;
124 private Button mPositiveButton;
125 private Button mNegativeButton;
126 private TextView mTitle;
127 private TextView mDescriptionText;
128 private Listener mListener;
129 private Spinner mSpinner;
130 private Drawable mSpinnerBackground;
131 private String mForcedAccountName;
132 private String mAccountName;
133 private String mAddAnotherAccount;
134 private ProfileDataCache mProfileData;
135 private boolean mSignedIn;
136 private boolean mPositionSetProgrammatically;
137 private int mDescriptionTextId;
138 private int mCancelButtonTextId;
139 private boolean mIsChildAccount;
140 private boolean mHorizontalModeEnabled = true;
141 private boolean mShowSettingsSpan = true;
142
143 public AccountFirstRunView(Context context, AttributeSet attrs) {
144 super(context, attrs);
145 mAccountManagerHelper = AccountManagerHelper.get(getContext().getApplica tionContext());
146 }
147
148 /**
149 * Initializes this view with profile images and full names.
150 * @param profileData ProfileDataCache that will be used to call to retrieve user account info.
151 */
152 public void init(ProfileDataCache profileData) {
153 setProfileDataCache(profileData);
154 }
155
156 /**
157 * Sets the profile data cache.
158 * @param profileData ProfileDataCache that will be used to call to retrieve user account info.
159 */
160 public void setProfileDataCache(ProfileDataCache profileData) {
161 mProfileData = profileData;
162 mProfileData.setObserver(this);
163 updateProfileImages();
164 }
165
166 @Override
167 protected void onFinishInflate() {
168 super.onFinishInflate();
169
170 mImageCarousel = (ImageCarousel) findViewById(R.id.image_slider);
171 mImageCarousel.setListener(this);
172
173 mPositiveButton = (Button) findViewById(R.id.positive_button);
174 mNegativeButton = (Button) findViewById(R.id.negative_button);
175
176 // A workaround for Android support library ignoring padding set in XML. b/20307607
177 int padding = getResources().getDimensionPixelSize(R.dimen.fre_button_pa dding);
178 ApiCompatibilityUtils.setPaddingRelative(mPositiveButton, padding, 0, pa dding, 0);
179 ApiCompatibilityUtils.setPaddingRelative(mNegativeButton, padding, 0, pa dding, 0);
180
181 mTitle = (TextView) findViewById(R.id.title);
182 mDescriptionText = (TextView) findViewById(R.id.description);
183 // For the spans to be clickable.
184 mDescriptionText.setMovementMethod(LinkMovementMethod.getInstance());
185 mDescriptionTextId = R.string.fre_account_choice_description;
186
187 // TODO(peconn): Ensure this is changed to R.string.cancel when used in Settings > Sign In.
188 mCancelButtonTextId = R.string.fre_skip_text;
189
190 // Set the invisible TextView to contain the longest text the visible Te xtView can hold.
191 // It assumes that the signed in description for child accounts is the l ongest text.
192 ((TextView) findViewById(R.id.longest_description)).setText(getSignedInD escription(true));
193
194 mAddAnotherAccount = getResources().getString(R.string.fre_add_account);
195
196 mSpinner = (Spinner) findViewById(R.id.google_accounts_spinner);
197 mSpinnerBackground = mSpinner.getBackground();
198 mArrayAdapter = new ArrayAdapter<CharSequence>(
199 getContext().getApplicationContext(), R.layout.fre_spinner_text) ;
200
201 mArrayAdapter.setDropDownViewResource(R.layout.fre_spinner_dropdown);
202 mSpinner.setAdapter(mArrayAdapter);
203 mSpinner.setOnItemSelectedListener(new SpinnerOnItemSelectedListener());
204
205 // Only set the spinner's content description right before the accessibi lity action is going
206 // to be performed. Otherwise, the the content description is read when the
207 // AccountFirstRunView is created because setting the spinner's adapter causes a
208 // TYPE_VIEW_SELECTED event. ViewPager loads the next and previous pages according to
209 // it's off-screen page limit, which is one by default, so without this the content
210 // description ends up being read when the card before this one shown.
211 mSpinner.setAccessibilityDelegate(new AccessibilityDelegate() {
212 @Override
213 public boolean performAccessibilityAction(View host, int action, Bun dle args) {
214 if (mSpinner.getContentDescription() == null) {
215 mSpinner.setContentDescription(getResources().getString(
216 R.string.accessibility_fre_account_spinner));
217 }
218 return super.performAccessibilityAction(host, action, args);
219 }
220 });
221
222 showSignInPage();
223 }
224
225 @Override
226 protected void onAttachedToWindow() {
227 super.onAttachedToWindow();
228 updateAccounts();
229 }
230
231 @Override
232 public void onWindowVisibilityChanged(int visibility) {
233 super.onWindowVisibilityChanged(visibility);
234 if (visibility == View.VISIBLE) {
235 if (updateAccounts()) {
236 // A new account has been added and the visibility has returned to us.
237 // The updateAccounts function will have selected the new accoun t.
238 // Shortcut to confirm sign in page.
239 showConfirmSignInPage();
240 }
241 }
242 }
243
244 @Override
245 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
246 // This assumes that view's layout_width is set to match_parent.
247 assert MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
248 int width = MeasureSpec.getSize(widthMeasureSpec);
249 int height = MeasureSpec.getSize(heightMeasureSpec);
250 LinearLayout content = (LinearLayout) findViewById(R.id.fre_content);
251 int paddingStart = 0;
252 if (mHorizontalModeEnabled
253 && width >= 2 * getResources().getDimension(R.dimen.fre_image_ca rousel_width)
254 && width > height) {
255 content.setOrientation(LinearLayout.HORIZONTAL);
256 paddingStart = getResources().getDimensionPixelSize(R.dimen.fre_marg in);
257 } else {
258 content.setOrientation(LinearLayout.VERTICAL);
259 }
260 ApiCompatibilityUtils.setPaddingRelative(content,
261 paddingStart,
262 content.getPaddingTop(),
263 ApiCompatibilityUtils.getPaddingEnd(content),
264 content.getPaddingBottom());
265 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
266 }
267
268 /**
269 * Changes the visuals slightly for when this view appears in the recent tab s page instead of
270 * in first run. For example, the title text is changed as well as the butto n style.
271 * This is currently used in the Recent Tabs Promo and the bookmarks page.
272 */
273 public void configureForRecentTabsOrBookmarksPage() {
274 mHorizontalModeEnabled = false;
275 mShowSettingsSpan = false;
276
277 setBackgroundResource(R.color.ntp_bg);
278 mTitle.setText(R.string.sign_in_to_chrome);
279
280 mCancelButtonTextId = R.string.cancel;
281 setUpCancelButton();
282
283 setPadding(0, 0, 0,
284 getResources().getDimensionPixelOffset(R.dimen.sign_in_promo_pad ding_bottom));
285 }
286
287 /**
288 * Changes the visuals slightly for when this view is shown in a subsequent run after user adds
289 * a Google account to the device.
290 */
291 public void configureForAddAccountPromo() {
292 int experimentGroup = SigninManager.getAndroidSigninPromoExperimentGroup ();
293 assert experimentGroup >= 0 && experimentGroup <= EXPERIMENT_MAX_VALUE;
294
295 if ((experimentGroup & EXPERIMENT_TITLE_VARIANT_MASK) != 0) {
296 mTitle.setText(R.string.make_chrome_yours);
297 }
298
299 mDescriptionTextId = (experimentGroup & EXPERIMENT_SUMMARY_VARIANT_MASK) != 0
300 ? R.string.sign_in_to_chrome_summary_variant : R.string.sign_in_ to_chrome_summary;
301
302 if ((experimentGroup & EXPERIMENT_LAYOUT_VARIANT_MASK) != 0) {
303 mImageCarousel.setVisibility(GONE);
304
305 ImageView illustrationView = new ImageView(getContext());
306 illustrationView.setImageResource(R.drawable.signin_promo_illustrati on);
307 illustrationView.setBackgroundColor(ApiCompatibilityUtils.getColor(g etResources(),
308 R.color.illustration_background_color));
309
310 LinearLayout linearLayout = (LinearLayout) findViewById(R.id.fre_acc ount_linear_layout);
311 linearLayout.addView(illustrationView, 0);
312 }
313 }
314
315 /**
316 * Enable or disable UI elements so the user can't select an account, cancel , etc.
317 *
318 * @param enabled The state to change to.
319 */
320 public void setButtonsEnabled(boolean enabled) {
321 mPositiveButton.setEnabled(enabled);
322 mNegativeButton.setEnabled(enabled);
323 }
324
325 /**
326 * Set the account selection event listener. See {@link Listener}
327 *
328 * @param listener The listener.
329 */
330 public void setListener(Listener listener) {
331 mListener = listener;
332 }
333
334 /**
335 * Refresh the list of available system account.
336 * @return Whether any new accounts were added (the first newly added accoun t will now be
337 * selected).
338 */
339 private boolean updateAccounts() {
340 if (mSignedIn) return false;
341
342 List<String> oldAccountNames = mAccountNames;
343 mAccountNames = mAccountManagerHelper.getGoogleAccountNames();
344 int accountToSelect = 0;
345 if (isInForcedAccountMode()) {
346 accountToSelect = mAccountNames.indexOf(mForcedAccountName);
347 if (accountToSelect < 0) {
348 mListener.onFailedToSetForcedAccount(mForcedAccountName);
349 return false;
350 }
351 } else {
352 accountToSelect = getIndexOfNewElement(
353 oldAccountNames, mAccountNames, mSpinner.getSelectedItemPosi tion());
354 }
355
356 mArrayAdapter.clear();
357 if (!mAccountNames.isEmpty()) {
358 mSpinner.setVisibility(View.VISIBLE);
359 mArrayAdapter.addAll(mAccountNames);
360 mArrayAdapter.add(mAddAnotherAccount);
361
362 setUpSignInButton(true);
363 mDescriptionText.setText(mDescriptionTextId);
364
365 } else {
366 mSpinner.setVisibility(View.GONE);
367 mArrayAdapter.add(mAddAnotherAccount);
368 setUpSignInButton(false);
369 mDescriptionText.setText(R.string.fre_no_account_choice_description) ;
370 }
371
372 if (mProfileData != null) mProfileData.update();
373 updateProfileImages();
374
375 mSpinner.setSelection(accountToSelect);
376 mAccountName = mArrayAdapter.getItem(accountToSelect).toString();
377 mImageCarousel.scrollTo(accountToSelect, false, false);
378
379 return oldAccountNames != null
380 && !(oldAccountNames.size() == mAccountNames.size()
381 && oldAccountNames.containsAll(mAccountNames));
382 }
383
384 /**
385 * Attempt to select a new element that is in the new list, but not in the o ld list.
386 * If no such element exist and both the new and the old lists are the same then keep
387 * the selection. Otherwise select the first element.
388 * @param oldList Old list of user accounts.
389 * @param newList New list of user accounts.
390 * @param oldIndex Index of the selected account in the old list.
391 * @return The index of the new element, if it does not exist but lists are the same the
392 * return the old index, otherwise return 0.
393 */
394 private static int getIndexOfNewElement(
395 List<String> oldList, List<String> newList, int oldIndex) {
396 if (oldList == null || newList == null) return 0;
397 if (oldList.size() == newList.size() && oldList.containsAll(newList)) re turn oldIndex;
398 if (oldList.size() + 1 == newList.size()) {
399 for (int i = 0; i < newList.size(); i++) {
400 if (!oldList.contains(newList.get(i))) return i;
401 }
402 }
403 return 0;
404 }
405
406 @Override
407 public void onProfileDownloaded(String accountId, String fullName, String gi venName,
408 Bitmap bitmap) {
409 updateProfileImages();
410 }
411
412 private void updateProfileImages() {
413 if (mProfileData == null) return;
414
415 int count = mAccountNames.size();
416
417 Bitmap[] images;
418 if (count == 0) {
419 images = new Bitmap[1];
420 images[0] = mProfileData.getImage(null);
421 } else {
422 images = new Bitmap[count];
423 for (int i = 0; i < count; ++i) {
424 images[i] = mProfileData.getImage(mAccountNames.get(i));
425 }
426 }
427
428 mImageCarousel.setImages(images);
429 updateProfileName();
430 }
431
432 private void updateProfileName() {
433 if (!mSignedIn) return;
434
435 String name = null;
436 if (mProfileData != null) {
437 if (mIsChildAccount) name = mProfileData.getGivenName(mAccountName);
438 if (name == null) name = mProfileData.getFullName(mAccountName);
439 }
440 if (name == null) name = mAccountName;
441 String text = String.format(getResources().getString(R.string.fre_hi_nam e), name);
442 mTitle.setText(text);
443 }
444
445 /**
446 * Updates the view to show that sign in has completed.
447 */
448 public void switchToSignedMode() {
449 showConfirmSignInPage();
450 }
451
452 private void showSignInPage() {
453 mSignedIn = false;
454 mTitle.setText(R.string.sign_in_to_chrome);
455
456 mSpinner.setEnabled(true);
457 mSpinner.setBackground(mSpinnerBackground);
458 mImageCarousel.setVisibility(VISIBLE);
459
460 setUpCancelButton();
461 updateAccounts();
462
463 mImageCarousel.setSignedInMode(false);
464 }
465
466 private void showConfirmSignInPage() {
467 mSignedIn = true;
468 updateProfileName();
469
470 mSpinner.setEnabled(false);
471 mSpinner.setBackground(null);
472 setUpConfirmButton();
473 setUpUndoButton();
474
475 if (mShowSettingsSpan) {
476 ClickableSpan settingsSpan = new ClickableSpan() {
477 @Override
478 public void onClick(View widget) {
479 mListener.onAccountSelected(mAccountName);
480 mListener.onSettingsClicked();
481 }
482
483 @Override
484 public void updateDrawState(TextPaint textPaint) {
485 textPaint.setColor(textPaint.linkColor);
486 textPaint.setUnderlineText(false);
487 }
488 };
489 mDescriptionText.setText(SpanApplier.applySpans(getSignedInDescripti on(mIsChildAccount),
490 new SpanInfo(SETTINGS_LINK_OPEN, SETTINGS_LINK_CLOSE, settin gsSpan)));
491 } else {
492 // If we aren't showing the span, get rid of the LINK1 annotations.
493 mDescriptionText.setText(getSignedInDescription(mIsChildAccount)
494 .replace(SETTINGS_LINK_OPEN, "")
495 .replace(SETTINGS_LINK_CLOSE, ""));
496 }
497
498 mImageCarousel.setVisibility(VISIBLE);
499 mImageCarousel.setSignedInMode(true);
500 }
501
502 private void setUpCancelButton() {
503 setNegativeButtonVisible(true);
504
505 mNegativeButton.setText(getResources().getText(mCancelButtonTextId));
506 mNegativeButton.setOnClickListener(new OnClickListener() {
507 @Override
508 public void onClick(View v) {
509 setButtonsEnabled(false);
510 mListener.onAccountSelectionCanceled();
511 }
512 });
513 }
514
515 private void setUpSignInButton(boolean hasAccounts) {
516 if (hasAccounts) {
517 mPositiveButton.setText(R.string.choose_account_sign_in);
518 mPositiveButton.setOnClickListener(new OnClickListener() {
519 @Override
520 public void onClick(View v) {
521 showConfirmSignInPage();
522 }
523 });
524 } else {
525 mPositiveButton.setText(R.string.fre_no_accounts);
526 mPositiveButton.setOnClickListener(new OnClickListener() {
527 @Override
528 public void onClick(View v) {
529 mListener.onNewAccount();
530 }
531 });
532 }
533 }
534
535 private void setUpUndoButton() {
536 setNegativeButtonVisible(!isInForcedAccountMode());
537 if (isInForcedAccountMode()) return;
538
539 mNegativeButton.setText(getResources().getText(R.string.undo));
540 mNegativeButton.setOnClickListener(new OnClickListener() {
541 @Override
542 public void onClick(View v) {
543 showSignInPage();
544 }
545 });
546 }
547
548 private void setUpConfirmButton() {
549 mPositiveButton.setText(getResources().getText(R.string.fre_accept));
550 mPositiveButton.setOnClickListener(new OnClickListener() {
551 @Override
552 public void onClick(View v) {
553 mListener.onAccountSelected(mAccountName);
554 mListener.onDoneClicked();
555 }
556 });
557 }
558
559 private void setNegativeButtonVisible(boolean enabled) {
560 if (enabled) {
561 mNegativeButton.setVisibility(View.VISIBLE);
562 findViewById(R.id.positive_button_end_padding).setVisibility(View.GO NE);
563 } else {
564 mNegativeButton.setVisibility(View.GONE);
565 findViewById(R.id.positive_button_end_padding).setVisibility(View.IN VISIBLE);
566 }
567 }
568
569 private String getSignedInDescription(boolean childAccount) {
570 if (childAccount) {
571 return getResources().getString(R.string.fre_signed_in_description) + '\n'
572 + getResources().getString(R.string.fre_signed_in_descriptio n_uca_addendum);
573 } else {
574 return getResources().getString(R.string.fre_signed_in_description);
575 }
576 }
577
578 /**
579 * @param isChildAccount Whether this view is for a child account.
580 */
581 public void setIsChildAccount(boolean isChildAccount) {
582 mIsChildAccount = isChildAccount;
583 }
584
585 /**
586 * Switches the view to "no choice, just a confirmation" forced-account mode .
587 * @param forcedAccountName An account that should be force-selected.
588 */
589 public void switchToForcedAccountMode(String forcedAccountName) {
590 mForcedAccountName = forcedAccountName;
591 updateAccounts();
592 assert TextUtils.equals(mAccountName, mForcedAccountName);
593 switchToSignedMode();
594 assert TextUtils.equals(mAccountName, mForcedAccountName);
595 }
596
597 /**
598 * @return Whether the view is in signed in mode.
599 */
600 public boolean isSignedIn() {
601 return mSignedIn;
602 }
603
604 /**
605 * @return Whether the view is in "no choice, just a confirmation" forced-ac count mode.
606 */
607 public boolean isInForcedAccountMode() {
608 return mForcedAccountName != null;
609 }
610
611 @Override
612 public void onPositionChanged(int i) {
613 mPositionSetProgrammatically = true;
614 mSpinner.setSelection(i);
615 }
616 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698