// Copyright 2016 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.signin; import android.content.Context; import android.content.Intent; import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import org.chromium.chrome.R; import org.chromium.chrome.browser.preferences.PreferencesLauncher; import org.chromium.chrome.browser.signin.AccountSigninActivity.AccessPoint; import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver; import org.chromium.chrome.browser.sync.ui.SyncCustomizationFragment; import org.chromium.components.signin.ChromeSigninController; import org.chromium.components.sync.AndroidSyncSettings; import org.chromium.components.sync.AndroidSyncSettings.AndroidSyncSettingsObserver; /** * A View that shows the user the next step they must complete to start syncing their data (eg. * Recent Tabs or Bookmarks). For example, if the user is not signed in, the View will prompt them * to do so and link to the AccountSigninActivity. * If inflated manually, {@link SigninAndSyncView#init()} must be called before attaching this View * to a ViewGroup. */ public class SigninAndSyncView extends LinearLayout implements AndroidSyncSettingsObserver, SignInStateObserver { private Listener mListener; @AccessPoint private int mAccessPoint; private boolean mInitialized; private final SigninManager mSigninManager; private TextView mTitle; private TextView mDescription; private Button mNegativeButton; private Button mPositiveButton; /** * A listener for the container of the SigninAndSyncView to be informed of certain user * interactions. */ public interface Listener { /** * The user has pressed 'no thanks' and expects the view to be removed from its parent. */ public void onViewDismissed(); } /** * A convenience method to inflate and initialize a SigninAndSyncView. * @param parent A parent used to provide LayoutParams (the SigninAndSyncView will not be * attached). * @param listener Listener for user interactions. * @param accessPoint Where the SigninAndSyncView is used. */ public static SigninAndSyncView create(ViewGroup parent, Listener listener, @AccessPoint int accessPoint) { SigninAndSyncView signinView = (SigninAndSyncView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.signin_and_sync_view, parent, false); signinView.init(listener, accessPoint); return signinView; } /** * Constructor for inflating from xml. */ public SigninAndSyncView(Context context, AttributeSet attrs) { super(context, attrs); mSigninManager = SigninManager.get(getContext()); } @Override protected void onFinishInflate() { super.onFinishInflate(); mTitle = (TextView) findViewById(R.id.title); mDescription = (TextView) findViewById(R.id.description); mNegativeButton = (Button) findViewById(R.id.no_thanks); mPositiveButton = (Button) findViewById(R.id.sign_in); } /** * Provide the information necessary for this class to function. * @param listener Listener for user interactions. * @param accessPoint Where this UI component is used. */ public void init(Listener listener, @AccessPoint int accessPoint) { mListener = listener; mAccessPoint = accessPoint; mInitialized = true; assert mAccessPoint == SigninAccessPoint.BOOKMARK_MANAGER || mAccessPoint == SigninAccessPoint.RECENT_TABS : "SigninAndSyncView only has strings for bookmark manager and recent tabs."; // The title stays the same no matter what action the user must take. if (mAccessPoint == SigninAccessPoint.BOOKMARK_MANAGER) { mTitle.setText(R.string.sync_your_bookmarks); } else { mTitle.setVisibility(View.GONE); } // We don't call update() here as it will be called in onAttachedToWindow(). } private void update() { ViewState viewState; if (!ChromeSigninController.get(getContext()).isSignedIn()) { viewState = getStateForSignin(); } else if (!AndroidSyncSettings.isMasterSyncEnabled(getContext())) { viewState = getStateForEnableAndroidSync(); } else if (!AndroidSyncSettings.isChromeSyncEnabled(getContext())) { viewState = getStateForEnableChromeSync(); } else { viewState = getStateForStartUsing(); } viewState.apply(mDescription, mPositiveButton, mNegativeButton); } /** * The ViewState class represents all the UI elements that can change for each variation of * this View. We use this to ensure each variation (created in the getStateFor* methods) * explicitly touches each UI element. */ private static class ViewState { private final int mDescriptionText; private final ButtonState mPositiveButtonState; private final ButtonState mNegativeButtonState; public ViewState(int mDescriptionText, ButtonState mPositiveButtonState, ButtonState mNegativeButtonState) { this.mDescriptionText = mDescriptionText; this.mPositiveButtonState = mPositiveButtonState; this.mNegativeButtonState = mNegativeButtonState; } public void apply(TextView description, Button positiveButton, Button negativeButton) { description.setText(mDescriptionText); mNegativeButtonState.apply(negativeButton); mPositiveButtonState.apply(positiveButton); } } /** * Classes to represent the state of a button that we are interested in, used to keep ViewState * tidy and provide some convenience methods. */ private interface ButtonState { void apply(Button button); } private static class ButtonAbsent implements ButtonState { @Override public void apply(Button button) { button.setVisibility(View.GONE); } } private static class ButtonPresent implements ButtonState { private final int mTextResource; private final OnClickListener mOnClickListener; public ButtonPresent(int textResource, OnClickListener onClickListener) { mTextResource = textResource; mOnClickListener = onClickListener; } @Override public void apply(Button button) { button.setVisibility(View.VISIBLE); button.setText(mTextResource); button.setOnClickListener(mOnClickListener); } } private ViewState getStateForSignin() { int descId = mAccessPoint == SigninAccessPoint.BOOKMARK_MANAGER ? R.string.bookmark_sign_in_promo_description : R.string.recent_tabs_sign_in_promo_description; ButtonState positiveButton = new ButtonPresent( R.string.sign_in_button, new OnClickListener() { @Override public void onClick(View view) { AccountSigninActivity .startAccountSigninActivity(getContext(), mAccessPoint); } }); ButtonState negativeButton; if (mAccessPoint == SigninAccessPoint.RECENT_TABS) { negativeButton = new ButtonAbsent(); } else { negativeButton = new ButtonPresent(R.string.no_thanks, new OnClickListener() { @Override public void onClick(View view) { mListener.onViewDismissed(); } }); } return new ViewState(descId, positiveButton, negativeButton); } private ViewState getStateForEnableAndroidSync() { assert mAccessPoint == SigninAccessPoint.RECENT_TABS : "Enable Android Sync should not be showing from bookmarks"; int descId = R.string.recent_tabs_sync_promo_enable_android_sync; ButtonState positiveButton = new ButtonPresent( R.string.open_settings_button, new OnClickListener() { @Override public void onClick(View v) { // TODO(crbug.com/557784): Like AccountManagementFragment, this would also // benefit from going directly to an account. Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[] {"com.google"}); getContext().startActivity(intent); } }); return new ViewState(descId, positiveButton, new ButtonAbsent()); } private ViewState getStateForEnableChromeSync() { int descId = mAccessPoint == SigninAccessPoint.BOOKMARK_MANAGER ? R.string.bookmarks_sync_promo_enable_sync : R.string.recent_tabs_sync_promo_enable_chrome_sync; ButtonState positiveButton = new ButtonPresent( R.string.enable_sync_button, new OnClickListener() { @Override public void onClick(View v) { PreferencesLauncher.launchSettingsPage(getContext(), SyncCustomizationFragment.class.getName()); } }); return new ViewState(descId, positiveButton, new ButtonAbsent()); } private ViewState getStateForStartUsing() { assert mAccessPoint == SigninAccessPoint.RECENT_TABS : "This should not be showing from bookmarks"; return new ViewState(R.string.ntp_recent_tabs_sync_promo_instructions, new ButtonAbsent(), new ButtonAbsent()); } @Override protected void onAttachedToWindow() { assert mInitialized : "init(...) must be called on SigninAndSyncView before use."; super.onAttachedToWindow(); mSigninManager.addSignInStateObserver(this); AndroidSyncSettings.registerObserver(getContext(), this); update(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mSigninManager.removeSignInStateObserver(this); AndroidSyncSettings.unregisterObserver(getContext(), this); } // SigninStateObserver @Override public void onSignedIn() { update(); } @Override public void onSignedOut() { update(); } // AndroidSyncStateObserver @Override public void androidSyncSettingsChanged() { update(); } }