Commit 36860c01 authored by asvitkine's avatar asvitkine Committed by Commit bot

Make FirstRunActivity extend AsyncInitializationActivity.

This is a follow-up to https://codereview.chromium.org/2519853004/
which made it possible to show the first FRE screen before native
is initialized, to support fetching the variations seed at that
time.

This CL further builds on that to make FirstRunActivity actually
be an AsyncInitializationActivity, so that it never needs to do
the synchronous native init path - instead waiting for async
init to be done before allowing the user to proceed.

When clicking Accept on the first FRE page (ToS and UMA), this
will now show a spinner while waiting for native init and the
variations seed fetch (whose timeout has been lowered to 3s).

BUG=632199

Review-Url: https://codereview.chromium.org/2561433002
Cr-Commit-Position: refs/heads/master@{#442919}
parent 841efc17
......@@ -93,4 +93,13 @@
android:textSize="@dimen/fre_button_text_size"
chrome:buttonColor="@color/light_active_color"
chrome:buttonRaised="false"/>
</org.chromium.chrome.browser.firstrun.FirstRunView>
\ No newline at end of file
<!-- Same location as the button; marginButtom is adjusted for the different size. -->
<ProgressBar
android:id="@+id/progress_spinner"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_marginBottom="22dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_width="24dp"
android:layout_height="24dp" />
</org.chromium.chrome.browser.firstrun.FirstRunView>
......@@ -10,21 +10,18 @@ import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.EmptyBrowserParts;
import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.metrics.UmaUtils;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.preferences.datareduction.DataReductionPromoUtils;
......@@ -35,7 +32,9 @@ import org.chromium.chrome.browser.util.IntentUtils;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
/**
......@@ -46,7 +45,7 @@ import java.util.concurrent.Callable;
* [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 AppCompatActivity implements FirstRunPageDelegate {
public class FirstRunActivity extends AsyncInitializationActivity implements FirstRunPageDelegate {
protected static final String TAG = "FirstRunActivity";
// Incoming parameters:
......@@ -103,6 +102,8 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
private boolean mPostNativePageSequenceCreated;
private boolean mNativeSideIsInitialized;
private Set<FirstRunPage> mPagesToNotifyOfNativeInit;
private boolean mDeferredCompleteFRE;
private ProfileDataCache mProfileDataCache;
private FirstRunViewPager mPager;
......@@ -131,12 +132,10 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
if (mShowWelcomePage) {
mPages.add(pageOf(ToSAndUMAFirstRunFragment.class));
mFreProgressStates.add(FRE_PROGRESS_WELCOME_SHOWN);
} else {
// Otherwise, if we're skipping past the welcome page, then init the
// native process and determine if data reduction proxy and signin
// pages should be shown - which both depend on native code.
createPostNativePageSequence();
}
// Other pages will be created by createPostNativePageSequence() after
// native has been initialized.
}
private void createPostNativePageSequence() {
......@@ -144,7 +143,6 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
// populates |mPages| which needs to be done even even if onNativeInitialized() was
// performed in a previous session.
if (mPostNativePageSequenceCreated) return;
ensureBrowserProcessInitialized();
mFirstRunFlowSequencer.onNativeInitialized(mFreProperties);
boolean notifyAdapter = false;
......@@ -168,14 +166,11 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
mPostNativePageSequenceCreated = true;
}
// Activity:
// AsyncInitializationActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ChromeBrowserInitializer.getInstance(this).handlePreNativeStartup(new EmptyBrowserParts());
public void setContentView() {
Bundle savedInstanceState = getSavedInstanceState();
if (savedInstanceState != null) {
mFreProperties = savedInstanceState;
} else if (getIntent() != null) {
......@@ -225,8 +220,6 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
mPager.setAdapter(mPagerAdapter);
recordFreProgressHistogram(mFreProgressStates.get(0));
skipPagesIfNecessary();
}
};
mFirstRunFlowSequencer.start();
......@@ -234,6 +227,43 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
recordFreProgressHistogram(FRE_PROGRESS_STARTED);
}
@Override
public void finishNativeInitialization() {
super.finishNativeInitialization();
mNativeSideIsInitialized = true;
if (mDeferredCompleteFRE) {
completeFirstRunExperience();
mDeferredCompleteFRE = false;
} else {
createPostNativePageSequence();
if (mPagesToNotifyOfNativeInit != null) {
for (FirstRunPage page : mPagesToNotifyOfNativeInit) {
page.onNativeInitialized();
}
}
mPagesToNotifyOfNativeInit = null;
skipPagesIfNecessary();
}
}
// Activity:
@Override
public void onAttachFragment(Fragment fragment) {
if (!(fragment instanceof FirstRunPage)) return;
FirstRunPage page = (FirstRunPage) fragment;
if (mNativeSideIsInitialized) {
page.onNativeInitialized();
return;
}
if (mPagesToNotifyOfNativeInit == null) {
mPagesToNotifyOfNativeInit = new HashSet<FirstRunPage>();
}
mPagesToNotifyOfNativeInit.add(page);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
......@@ -241,7 +271,7 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
}
@Override
protected void onPause() {
public void onPause() {
super.onPause();
flushPersistentData();
}
......@@ -253,7 +283,7 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
}
@Override
protected void onStart() {
public void onStart() {
super.onStart();
// Since the FRE may be shown before any tab is shown, mark that this is the point at
// which Chrome went to foreground. This is needed as otherwise an assert will be hit
......@@ -325,7 +355,10 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
@Override
public void completeFirstRunExperience() {
ensureBrowserProcessInitialized();
if (!mNativeSideIsInitialized) {
mDeferredCompleteFRE = true;
return;
}
if (!TextUtils.isEmpty(mResultSignInAccountName)) {
boolean defaultAccountName =
sGlue.isDefaultAccountName(getApplicationContext(), mResultSignInAccountName);
......@@ -396,13 +429,6 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
@Override
public void acceptTermsOfService(boolean allowCrashUpload) {
// At this point, we're advancing past the first page, which has no native
// dependencies to further pages in the sequence, if any. These require
// native to be initialized and will be added by the call below if needed.
// Additionally, the calls later in this function also require native
// to be initialized.
createPostNativePageSequence();
// If default is true then it corresponds to opt-out and false corresponds to opt-in.
UmaUtils.recordMetricsReportingDefaultOptIn(!DEFAULT_METRICS_AND_CRASH_REPORTING);
sGlue.acceptTermsOfService(allowCrashUpload);
......@@ -504,28 +530,11 @@ public class FirstRunActivity extends AppCompatActivity implements FirstRunPageD
while (currentPageIndex < mPagerAdapter.getCount()) {
FirstRunPage currentPage = (FirstRunPage) mPagerAdapter.getItem(currentPageIndex);
if (!currentPage.shouldSkipPageOnCreate(getApplicationContext())) return;
// If we're advancing to the next page, ensure to init the post native page
// sequence - as every page except the first requires that to be initialized.
// This is a no-op if it has already been done.
createPostNativePageSequence();
if (!jumpToPage(currentPageIndex + 1)) return;
currentPageIndex = mPager.getCurrentItem();
}
}
private void ensureBrowserProcessInitialized() {
if (mNativeSideIsInitialized) return;
try {
ChromeBrowserInitializer.getInstance(this).handlePostNativeStartup(
false, new EmptyBrowserParts());
mNativeSideIsInitialized = true;
} catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e);
abortFirstRunExperience();
return;
}
}
private void recordFreProgressHistogram(int state) {
if (mFreProperties.getBoolean(FirstRunActivity.EXTRA_COMING_FROM_CHROME_ICON)) {
sMobileFreProgressMainIntentHistogram.record(state);
......
......@@ -48,6 +48,13 @@ public class FirstRunPage extends Fragment {
return false;
}
/**
* @return Whether the page should be re-created when notifyDataSetChanged() fires.
*/
public boolean shouldRecreatePageOnDataChange() {
return true;
}
/**
* @return Passed arguments if any, or saved instance state if any, or an empty bundle.
*/
......@@ -68,4 +75,9 @@ public class FirstRunPage extends Fragment {
protected void advanceToNextPage() {
getPageDelegate().advanceToNextPage();
}
}
\ No newline at end of file
/**
* Notifies this page that native has been initialized.
*/
protected void onNativeInitialized() {}
}
......@@ -64,6 +64,7 @@ public interface FirstRunPageDelegate {
/**
* Notifies all interested parties that the user has accepted Chrome Terms of Service.
* Must be called only after native has been initialized.
* @param allowCrashUpload True if the user allows to upload crash dumps and collect stats.
*/
void acceptTermsOfService(boolean allowCrashUpload);
......
......@@ -68,8 +68,11 @@ class FirstRunPagerAdapter extends FragmentStatePagerAdapter {
@Override
public int getItemPosition(Object object) {
// We do not keep track of constructed objects, but we want the pages to be recreated
// on notifyDataSetChanged. Hence, tell the view that it needs to refresh the objects.
// Each page can specify whether it should be re-created or not on a notifyDataSetChanged.
if (object instanceof FirstRunPage) {
FirstRunPage page = (FirstRunPage) object;
return page.shouldRecreatePageOnDataChange() ? POSITION_NONE : POSITION_UNCHANGED;
}
return POSITION_NONE;
}
}
\ No newline at end of file
}
......@@ -6,7 +6,6 @@ package org.chromium.chrome.browser.firstrun;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
......@@ -27,9 +26,8 @@ import org.chromium.ui.text.SpanApplier.SpanInfo;
*/
public class LightweightFirstRunActivity extends FirstRunActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void setContentView() {
super.setContentView();
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)) {
completeFirstRunExperience();
}
......
......@@ -31,6 +31,10 @@ public class ToSAndUMAFirstRunFragment extends FirstRunPage {
private Button mAcceptButton;
private CheckBox mSendReportCheckBox;
private TextView mTosAndPrivacy;
private View mTitle;
private View mProgressSpinner;
private boolean mNativeInitialized;
private boolean mTriggerAcceptAfterNativeInit;
@Override
public View onCreateView(
......@@ -42,6 +46,9 @@ public class ToSAndUMAFirstRunFragment extends FirstRunPage {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTitle = view.findViewById(R.id.title);
mProgressSpinner = view.findViewById(R.id.progress_spinner);
mProgressSpinner.setVisibility(View.GONE);
mAcceptButton = (Button) view.findViewById(R.id.terms_accept);
mSendReportCheckBox = (CheckBox) view.findViewById(R.id.send_report_checkbox);
mTosAndPrivacy = (TextView) view.findViewById(R.id.tos_and_privacy);
......@@ -49,7 +56,7 @@ public class ToSAndUMAFirstRunFragment extends FirstRunPage {
mAcceptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getPageDelegate().acceptTermsOfService(mSendReportCheckBox.isChecked());
acceptTermsOfService();
}
});
......@@ -87,12 +94,22 @@ public class ToSAndUMAFirstRunFragment extends FirstRunPage {
mTosAndPrivacy.setText(SpanApplier.applySpans(getString(R.string.fre_tos_and_privacy),
new SpanInfo("<LINK1>", "</LINK1>", clickableTermsSpan),
new SpanInfo("<LINK2>", "</LINK2>", clickablePrivacySpan)));
// If this page should be skipped, hide all the UI elements except for the
// Chrome logo and the spinner.
if (FirstRunStatus.shouldSkipWelcomePage()) {
setSpinnerVisible(true);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && mSendReportCheckBox != null) {
if (!isVisibleToUser) {
// Restore original enabled & visibility states, in case the user returns to the page.
setSpinnerVisible(false);
} else if (mSendReportCheckBox != null) {
// On certain versions of Android, the checkbox will appear unchecked upon revisiting
// the page. Force it to the end state of the drawable animation as a work around.
// crbug.com/666258
......@@ -104,4 +121,42 @@ public class ToSAndUMAFirstRunFragment extends FirstRunPage {
public boolean shouldSkipPageOnCreate(Context appContext) {
return FirstRunStatus.shouldSkipWelcomePage();
}
@Override
public boolean shouldRecreatePageOnDataChange() {
// Specify that this page shouldn't be re-created on notifyDataSetChanged(), so
// that state like mTriggerAcceptAfterNativeInit can be preserved on the instance
// when native is initialized.
return false;
}
@Override
protected void onNativeInitialized() {
assert !mNativeInitialized;
mNativeInitialized = true;
if (mTriggerAcceptAfterNativeInit) acceptTermsOfService();
}
private void acceptTermsOfService() {
if (!mNativeInitialized) {
mTriggerAcceptAfterNativeInit = true;
setSpinnerVisible(true);
return;
}
mTriggerAcceptAfterNativeInit = false;
getPageDelegate().acceptTermsOfService(mSendReportCheckBox.isChecked());
}
private void setSpinnerVisible(boolean spinnerVisible) {
// When the progress spinner is visibile, we hide the other UI elements so that
// the user can't interact with them.
int otherElementsVisible = spinnerVisible ? View.INVISIBLE : View.VISIBLE;
mTitle.setVisibility(otherElementsVisible);
mAcceptButton.setVisibility(otherElementsVisible);
mTosAndPrivacy.setVisibility(otherElementsVisible);
mSendReportCheckBox.setVisibility(otherElementsVisible);
mProgressSpinner.setVisibility(spinnerVisible ? View.VISIBLE : View.GONE);
}
}
......@@ -33,8 +33,8 @@ public class VariationsSeedService extends IntentService {
private static final String VARIATIONS_SERVER_URL =
"https://clientservices.googleapis.com/chrome-variations/seed?osname=android";
private static final int BUFFER_SIZE = 4096;
private static final int READ_TIMEOUT = 10000; // time in ms
private static final int REQUEST_TIMEOUT = 15000; // time in ms
private static final int READ_TIMEOUT = 3000; // time in ms
private static final int REQUEST_TIMEOUT = 1000; // time in ms
// Values for the "Variations.FirstRun.SeedFetchResult" sparse histogram, which also logs
// HTTP result codes. These are negative so that they don't conflict with the HTTP codes.
......
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