Index: chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4d0380962e8568943f7f1e89e9b2b55c3fb03ea1 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java |
@@ -0,0 +1,437 @@ |
+// 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.Activity; |
+import android.app.Fragment; |
+import android.content.Intent; |
+import android.os.Bundle; |
+import android.support.v4.view.ViewPager; |
+import android.support.v7.app.ActionBarActivity; |
+import android.text.TextUtils; |
+ |
+import org.chromium.base.ApplicationStatus; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.ChromiumApplication; |
+import org.chromium.chrome.browser.metrics.UmaBridge; |
+import org.chromium.chrome.browser.profiles.Profile; |
+ |
+import java.lang.ref.WeakReference; |
+import java.lang.reflect.Constructor; |
+import java.util.ArrayList; |
+import java.util.List; |
+import java.util.concurrent.Callable; |
+ |
+/** |
+ * Handles the First Run Experience sequences shown to the user launching Chrome for the first time. |
+ * It supports only a simple format of FRE: |
+ * [Welcome] |
+ * [Intro pages...] |
+ * [Sign-in page] |
+ * The activity might be run more than once, e.g. 1) for ToS and sign-in, and 2) for intro. |
+ */ |
+public class FirstRunActivity extends ActionBarActivity implements FirstRunPageDelegate { |
+ protected static final String TAG = "FirstRunActivity"; |
+ |
+ // Incoming parameters: |
+ public static final String ORIGINAL_INTENT = "OriginalIntent"; |
+ public static final String FIRE_ORIGINAL_INTENT = "FireOriginalIntent"; |
+ public static final String COMING_FROM_CHROME_ICON = "ComingFromChromeIcon"; |
+ public static final String USE_FRE_FLOW_SEQUENCER = "UseFreFlowSequencer"; |
+ |
+ public static final String SHOW_WELCOME_PAGE = "ShowWelcome"; |
+ public static final String SKIP_WELCOME_PAGE_IF_ACCEPTED_TOS = "SkipWelcomePageIfAcceptedToS"; |
+ public static final String SHOW_INTRO_BITMAP = "ShowIntroBitmap"; |
+ public static final String SKIP_ALL_INTRO = "SkipAllIntro"; // Marks all intros as seen. |
+ public static final String SHOW_SIGNIN_PAGE = "ShowSignIn"; |
+ |
+ // Outcoming results: |
+ public static final String RESULT_CLOSE_APP = "Close App"; |
+ public static final String RESULT_SIGNIN_ACCOUNT_NAME = "ResultSignInTo"; |
+ public static final String RESULT_SHOW_SYNC_SETTINGS = "ResultShowSyncSettings"; |
+ |
+ // UMA constants. |
+ private static final String UMA_SIGNIN_CHOICE = "MobileFre.SignInChoice"; |
+ private static final int SIGNIN_SETTINGS_DEFAULT_ACCOUNT = 0; |
+ private static final int SIGNIN_SETTINGS_ANOTHER_ACCOUNT = 1; |
+ private static final int SIGNIN_ACCEPT_DEFAULT_ACCOUNT = 2; |
+ private static final int SIGNIN_ACCEPT_ANOTHER_ACCOUNT = 3; |
+ private static final int SIGNIN_NO_THANKS = 4; |
+ private static final int SIGNIN_OPTION_COUNT = 5; |
+ |
+ @VisibleForTesting |
+ static FirstRunGlue sGlue = new FirstRunGlueImpl(); |
+ |
+ private boolean mShowWelcomePage = true; |
+ |
+ private String mResultSignInAccountName; |
+ private boolean mResultShowSyncSettings; |
+ |
+ // TODO(aurimas): make this private once all of FirstRunActivity is upstreamed. |
+ protected boolean mNativeSideIsInitialized; |
+ |
+ private ProfileDataCache mProfileDataCache; |
+ private ViewPager mPager; |
+ |
+ private Bundle mFreProperties; |
+ |
+ private List<Callable<FirstRunPage>> mPages; |
+ private int mSkipIntroPageNumber; |
+ |
+ /** |
+ * The pager adapter, which provides the pages to the view pager widget. |
+ */ |
+ private FirstRunPagerAdapter mPagerAdapter; |
+ |
+ /** |
+ * Defines a sequence of pages to be shown (depending on parameters etc). |
+ */ |
+ private void createPageSequence() { |
+ mPages = new ArrayList<Callable<FirstRunPage>>(); |
+ |
+ // An optional welcome page. |
+ if (mShowWelcomePage) mPages.add(pageOf(ToSAndUMAFirstRunFragment.class)); |
+ |
+ // An optional sequence of intro pages. |
+ if (!mFreProperties.getBoolean(SKIP_ALL_INTRO)) { |
+ final long bitmap = mFreProperties.getLong(SHOW_INTRO_BITMAP); |
+ |
+ // "Hera" (recents/tabs) promo. |
+ if (((bitmap & FirstRunIntroPage.INTRO_RECENTS) != 0) |
+ && sGlue.isDocumentModeEligible(getApplicationContext())) { |
+ mPages.add(pageOf(FirstRunIntroRecentsPage.class)); |
+ } |
+ } |
+ |
+ // Set the anchor to jump to if the user skips intro pages. |
+ mSkipIntroPageNumber = mPages.size(); |
+ |
+ // An optional sign-in page. |
+ if (mFreProperties.getBoolean(SHOW_SIGNIN_PAGE)) { |
+ mPages.add(pageOf(AccountFirstRunFragment.class)); |
+ } |
+ } |
+ |
+ // Activity: |
+ |
+ @Override |
+ protected void onCreate(Bundle savedInstanceState) { |
+ initializeBrowserProcess(); |
+ |
+ super.onCreate(savedInstanceState); |
+ setFinishOnTouchOutside(false); |
+ |
+ if (savedInstanceState != null) { |
+ mFreProperties = savedInstanceState; |
+ } else if (getIntent() != null) { |
+ mFreProperties = getIntent().getExtras(); |
+ } else { |
+ mFreProperties = new Bundle(); |
+ } |
+ |
+ mPager = new ViewPager(this); |
+ mPager.setId(R.id.fre_pager); |
+ setContentView(mPager); |
+ android.util.Log.i("FirstRunActivity", "onCreate: after setContentView"); |
+ |
+ mProfileDataCache = new ProfileDataCache(FirstRunActivity.this, null); |
+ mProfileDataCache.setProfile(Profile.getLastUsedProfile()); |
+ new FirstRunFlowSequencer(this, mFreProperties) { |
+ @Override |
+ public void onFlowIsKnown(Activity activity, Bundle freProperties) { |
+ android.util.Log.i("FirstRunActivity", "in onFlowIsKnown"); |
+ |
+ if (freProperties == null) { |
+ completeFirstRunExperience(); |
+ return; |
+ } |
+ |
+ mFreProperties = freProperties; |
+ mShowWelcomePage = mFreProperties.getBoolean(SHOW_WELCOME_PAGE); |
+ if (mShowWelcomePage |
+ && mFreProperties.getBoolean(SKIP_WELCOME_PAGE_IF_ACCEPTED_TOS)) { |
+ mShowWelcomePage = !sGlue.didAcceptTermsOfService(getApplicationContext()); |
+ } |
+ createPageSequence(); |
+ |
+ if (TextUtils.isEmpty(mResultSignInAccountName)) { |
+ mResultSignInAccountName = mFreProperties.getString( |
+ AccountFirstRunFragment.FORCE_SIGNIN_ACCOUNT_TO); |
+ } |
+ |
+ if (mPages.size() == 0) { |
+ completeFirstRunExperience(); |
+ return; |
+ } |
+ |
+ mPagerAdapter = |
+ new FirstRunPagerAdapter(getFragmentManager(), mPages, mFreProperties); |
+ stopProgressionIfNotAcceptedTermsOfService(); |
+ mPager.setAdapter(mPagerAdapter); |
+ |
+ skipPagesIfNecessary(); |
+ android.util.Log.i("FirstRunActivity", "before return from onFlowIsKnown"); |
+ } |
+ }.start(); |
+ android.util.Log.i("FirstRunActivity", "onCreate: after starting the sequencer"); |
+ } |
+ |
+ @Override |
+ protected void onSaveInstanceState(Bundle outState) { |
+ super.onSaveInstanceState(outState); |
+ outState.putAll(mFreProperties); |
+ } |
+ |
+ @Override |
+ protected void onPause() { |
+ super.onPause(); |
+ flushPersistentData(); |
+ } |
+ |
+ @Override |
+ protected void onResumeFragments() { |
+ skipPagesIfNecessary(); |
+ } |
+ |
+ @Override |
+ protected void onDestroy() { |
+ super.onDestroy(); |
+ mProfileDataCache.onDestroy(); |
+ } |
+ |
+ @Override |
+ protected void onStart() { |
+ super.onStart(); |
+ stopProgressionIfNotAcceptedTermsOfService(); |
+ if (!mFreProperties.getBoolean(USE_FRE_FLOW_SEQUENCER)) { |
+ if (FirstRunStatus.getFirstRunFlowComplete(this) |
+ && FirstRunIntroPage.wereAllNecessaryPagesShown(this)) { |
+ // This is a parallel flow that needs to be refreshed/re-fired. |
+ // Signal the FRE flow completion and re-launch the original intent. |
+ completeFirstRunExperience(); |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void onBackPressed() { |
+ // Terminate if we are still waiting for the native or for Android EDU / GAIA Child checks. |
+ if (mPagerAdapter == null) { |
+ abortFirstRunExperience(); |
+ return; |
+ } |
+ |
+ Object currentItem = mPagerAdapter.instantiateItem(mPager, mPager.getCurrentItem()); |
+ if (currentItem instanceof FirstRunPage) { |
+ FirstRunPage page = (FirstRunPage) currentItem; |
+ if (page.interceptBackPressed()) return; |
+ } |
+ |
+ if (mPager.getCurrentItem() == 0) { |
+ abortFirstRunExperience(); |
+ } else { |
+ mPager.setCurrentItem(mPager.getCurrentItem() - 1); |
+ } |
+ } |
+ |
+ // FirstRunPageDelegate: |
+ |
+ @Override |
+ public ProfileDataCache getProfileDataCache() { |
+ return mProfileDataCache; |
+ } |
+ |
+ @Override |
+ public void advanceToNextPage() { |
+ jumpToPage(mPager.getCurrentItem() + 1, true); |
+ } |
+ |
+ @Override |
+ public void skipIntroPages() { |
+ jumpToPage(mSkipIntroPageNumber, false); |
+ } |
+ |
+ @Override |
+ public void recreateCurrentPage() { |
+ mPagerAdapter.notifyDataSetChanged(); |
+ } |
+ |
+ @Override |
+ public void abortFirstRunExperience() { |
+ Intent intent = new Intent(); |
+ if (mFreProperties != null) intent.putExtras(mFreProperties); |
+ intent.putExtra(RESULT_CLOSE_APP, true); |
+ finishAllFREActivities(Activity.RESULT_CANCELED, intent); |
+ } |
+ |
+ @Override |
+ public void completeFirstRunExperience() { |
+ if (!TextUtils.isEmpty(mResultSignInAccountName)) { |
+ boolean defaultAccountName = |
+ sGlue.isDefaultAccountName(getApplicationContext(), mResultSignInAccountName); |
+ int choice; |
+ if (mResultShowSyncSettings) { |
+ if (defaultAccountName) { |
+ choice = SIGNIN_SETTINGS_DEFAULT_ACCOUNT; |
+ } else { |
+ choice = SIGNIN_SETTINGS_ANOTHER_ACCOUNT; |
+ } |
+ } else { |
+ if (defaultAccountName) { |
+ choice = SIGNIN_ACCEPT_DEFAULT_ACCOUNT; |
+ } else { |
+ choice = SIGNIN_ACCEPT_ANOTHER_ACCOUNT; |
+ } |
+ } |
+ RecordHistogram.recordEnumeratedHistogram( |
+ UMA_SIGNIN_CHOICE, choice, SIGNIN_OPTION_COUNT); |
+ } |
+ |
+ mFreProperties.putString(RESULT_SIGNIN_ACCOUNT_NAME, mResultSignInAccountName); |
+ mFreProperties.putBoolean(RESULT_SHOW_SYNC_SETTINGS, mResultShowSyncSettings); |
+ FirstRunFlowSequencer.markFlowAsCompleted(this, mFreProperties); |
+ |
+ if (mFreProperties.getBoolean(FirstRunActivity.FIRE_ORIGINAL_INTENT)) { |
+ Intent originalIntent = mFreProperties.getParcelable(FirstRunActivity.ORIGINAL_INTENT); |
+ startActivity(originalIntent); |
+ } |
+ |
+ Intent resultData = new Intent(); |
+ resultData.putExtras(mFreProperties); |
+ finishAllFREActivities(Activity.RESULT_OK, resultData); |
+ } |
+ |
+ @Override |
+ public void onSigninDialogShown() { |
+ UmaBridge.freSignInShown(); |
+ } |
+ |
+ @Override |
+ public void refuseSignIn() { |
+ RecordHistogram.recordEnumeratedHistogram( |
+ UMA_SIGNIN_CHOICE, SIGNIN_NO_THANKS, SIGNIN_OPTION_COUNT); |
+ mResultSignInAccountName = null; |
+ mResultShowSyncSettings = false; |
+ } |
+ |
+ @Override |
+ public void acceptSignIn(String accountName) { |
+ mResultSignInAccountName = accountName; |
+ } |
+ |
+ @Override |
+ public void askToOpenSyncSettings() { |
+ mResultShowSyncSettings = true; |
+ } |
+ |
+ @Override |
+ public boolean didAcceptTermsOfService() { |
+ return sGlue.didAcceptTermsOfService(getApplicationContext()); |
+ } |
+ |
+ @Override |
+ public boolean isNeverUploadCrashDump() { |
+ return sGlue.isNeverUploadCrashDump(getApplicationContext()); |
+ } |
+ |
+ @Override |
+ public void acceptTermsOfService(boolean allowCrashUpload) { |
+ sGlue.acceptTermsOfService(getApplicationContext(), allowCrashUpload); |
+ flushPersistentData(); |
+ stopProgressionIfNotAcceptedTermsOfService(); |
+ jumpToPage(mPager.getCurrentItem() + 1, true); |
+ } |
+ |
+ @Override |
+ public void openAccountAdder(Fragment fragment) { |
+ sGlue.openAccountAdder(fragment); |
+ } |
+ |
+ protected void flushPersistentData() { |
+ if (mNativeSideIsInitialized) ChromiumApplication.flushPersistentData(); |
+ } |
+ |
+ private static void finishAllFREActivities(int result, Intent data) { |
+ List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities(); |
+ for (WeakReference<Activity> weakActivity : activities) { |
+ Activity activity = weakActivity.get(); |
+ if (activity instanceof FirstRunActivity) { |
+ activity.setResult(result, data); |
+ activity.finish(); |
+ } |
+ } |
+ } |
+ |
+ |
+ /** |
+ * Transitions to a given page. |
+ * @return Whether the transition to a given page was allowed. |
+ * @param position A page index to transition to. |
+ * @param smooth Whether the transition should be smooth. |
+ */ |
+ private boolean jumpToPage(int position, boolean smooth) { |
+ if (mShowWelcomePage && !didAcceptTermsOfService()) { |
+ return position == 0; |
+ } |
+ if (position >= mPagerAdapter.getCount()) { |
+ completeFirstRunExperience(); |
+ return false; |
+ } |
+ mPager.setCurrentItem(position, smooth); |
+ return true; |
+ } |
+ |
+ private void stopProgressionIfNotAcceptedTermsOfService() { |
+ if (mPagerAdapter == null) return; |
+ mPagerAdapter.setStopAtTheFirstPage(mShowWelcomePage && !didAcceptTermsOfService()); |
+ } |
+ |
+ private void skipPagesIfNecessary() { |
+ if (mPagerAdapter == null) return; |
+ |
+ int currentPageIndex = mPager.getCurrentItem(); |
+ while (currentPageIndex < mPagerAdapter.getCount()) { |
+ FirstRunPage currentPage = (FirstRunPage) mPagerAdapter.getItem(currentPageIndex); |
+ if (!currentPage.shouldSkipPageOnResume(getApplicationContext())) return; |
+ if (!jumpToPage(currentPageIndex + 1, false)) return; |
+ currentPageIndex = mPager.getCurrentItem(); |
+ } |
+ } |
+ |
+ protected void initializeBrowserProcess() { |
+ // TODO(aurimas): implement this once browser process initialization is upstreamed. |
+ } |
+ |
+ /** |
+ * Creates a trivial page constructor for a given page type. |
+ * @param clazz The .class of the page type. |
+ * @return The simple constructor for a given page type (no parameters, no tuning). |
+ */ |
+ public static Callable<FirstRunPage> pageOf(final Class<? extends FirstRunPage> clazz) { |
+ return new Callable<FirstRunPage>() { |
+ @Override |
+ public FirstRunPage call() throws Exception { |
+ Constructor<? extends FirstRunPage> constructor = clazz.getDeclaredConstructor(); |
+ return constructor.newInstance(); |
+ } |
+ }; |
+ } |
+ |
+ @Override |
+ public void showEmbedContentViewActivity(int title, int url) { |
+ // TODO(aurimas): implement this once EmbededContentViewActivity is upstreamed. |
+ } |
+ |
+ public void showSignInNotification() { |
+ // TODO(aurimas): implement this once GoogleServicesManager is upstreamed. |
+ } |
+ |
+ @Override |
+ public void openDocumentModeSettings() { |
+ // TODO(aurimas): implement opening settings once DocumentModeSettings is upstreamed. |
+ } |
+} |