--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -140,16 +140,17 @@ android {
} else {
exclude 'org/mozilla/gecko/adjust/AdjustHelper.java'
}
if (mozconfig.substs.MOZ_ANDROID_MMA) {
exclude 'org/mozilla/gecko/mma/MmaStubImp.java'
} else {
exclude 'org/mozilla/gecko/mma/MmaLeanplumImp.java'
+ exclude 'org/mozilla/gecko/mma/LeanplumVariables.java'
}
if (!mozconfig.substs.MOZ_ANDROID_GCM) {
exclude 'org/mozilla/gecko/gcm/**/*.java'
exclude 'org/mozilla/gecko/push/**/*.java'
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -36,17 +36,16 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
-import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.MenuItemCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -89,17 +88,17 @@ import org.mozilla.gecko.db.SuggestedSit
import org.mozilla.gecko.delegates.BookmarkStateChangeDelegate;
import org.mozilla.gecko.delegates.BrowserAppDelegate;
import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
import org.mozilla.gecko.delegates.ScreenshotDelegate;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.distribution.DistributionStoreCallback;
import org.mozilla.gecko.dlc.DownloadContentService;
import org.mozilla.gecko.extensions.ExtensionPermissionsHelper;
-import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
+import org.mozilla.gecko.firstrun.OnboardingHelper;
import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
import org.mozilla.gecko.home.BrowserSearch;
import org.mozilla.gecko.home.HomeBanner;
import org.mozilla.gecko.home.HomeConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.home.HomeConfigPrefsBackend;
import org.mozilla.gecko.home.HomeFragment;
@@ -157,16 +156,17 @@ import org.mozilla.gecko.util.ActivityUt
import org.mozilla.gecko.util.ContextUtils;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GamepadUtils;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.IntentUtils;
import org.mozilla.gecko.util.MenuUtils;
+import org.mozilla.gecko.util.NetworkUtils;
import org.mozilla.gecko.util.PrefUtils;
import org.mozilla.gecko.util.ShortcutUtils;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.WindowUtil;
import org.mozilla.gecko.widget.ActionModePresenter;
import org.mozilla.gecko.widget.AnchoredPopup;
import org.mozilla.gecko.widget.AnimatedProgressBar;
@@ -181,17 +181,16 @@ import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.UUID;
import java.util.regex.Pattern;
import static org.mozilla.gecko.mma.MmaDelegate.NEW_TAB;
public class BrowserApp extends GeckoApp
implements ActionModePresenter,
AnchoredPopup.OnVisibilityChangeListener,
BookmarkEditFragment.Callbacks,
@@ -199,17 +198,18 @@ public class BrowserApp extends GeckoApp
BrowserSearch.OnSearchListener,
DynamicToolbarAnimator.ToolbarChromeProxy,
LayoutInflater.Factory,
LightweightTheme.OnChangeListener,
OnUrlOpenListener,
OnUrlOpenInBackgroundListener,
PropertyAnimator.PropertyAnimationListener,
TabsPanel.TabsLayoutChangeListener,
- View.OnKeyListener {
+ View.OnKeyListener,
+ OnboardingHelper.OnboardingListener {
private static final String LOGTAG = "GeckoBrowserApp";
private static final int TABS_ANIMATION_DURATION = 450;
// Intent String extras used to specify custom Switchboard configurations.
private static final String INTENT_KEY_SWITCHBOARD_SERVER = "switchboard-server";
// TODO: Replace with kinto endpoint.
@@ -228,38 +228,30 @@ public class BrowserApp extends GeckoApp
public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS = 3002;
public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE = 3003;
public static final int ACTIVITY_REQUEST_TRIPLE_READERVIEW = 4001;
public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK = 4002;
public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE = 4003;
public static final String ACTION_VIEW_MULTIPLE = AppConstants.ANDROID_PACKAGE_NAME + ".action.VIEW_MULTIPLE";
- @RobocopTarget
- public static final String EXTRA_SKIP_STARTPANE = "skipstartpane";
private static final String EOL_NOTIFIED = "eol_notified";
- /**
- * Be aware of {@link org.mozilla.gecko.fxa.EnvironmentUtils.GECKO_PREFS_FIRSTRUN_UUID}.
- */
- private static final String FIRSTRUN_UUID = "firstrun_uuid";
-
private BrowserSearch mBrowserSearch;
private View mBrowserSearchContainer;
public ViewGroup mBrowserChrome;
public ViewFlipper mActionBarFlipper;
public ActionModeCompatView mActionBar;
private VideoPlayer mVideoPlayer;
private BrowserToolbar mBrowserToolbar;
private View doorhangerOverlay;
// We can't name the TabStrip class because it's not included on API 9.
private TabStripInterface mTabStrip;
private AnimatedProgressBar mProgressView;
- private FirstrunAnimationContainer mFirstrunAnimationContainer;
private HomeScreen mHomeScreen;
private TabsPanel mTabsPanel;
private boolean showSplashScreen = false;
private SplashScreen splashScreen;
/**
* Container for the home screen implementation. This will be populated with any valid
* home screen implementation (currently that is just the HomePager, but that will be extended
@@ -428,16 +420,17 @@ public class BrowserApp extends GeckoApp
new PostUpdateHandler(),
mTelemetryCorePingDelegate,
new OfflineTabStatusDelegate(),
new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
));
@NonNull
private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
+ private OnboardingHelper mOnboardingHelper; // Contains reference to Context - DO NOT LEAK!
private boolean mHasResumed;
@Override
public View onCreateView(final View parent, final String name, final Context context, final AttributeSet attrs) {
final View view;
if (BrowserToolbar.class.getName().equals(name)) {
view = BrowserToolbar.create(context, attrs);
@@ -744,16 +737,17 @@ public class BrowserApp extends GeckoApp
app.prepareLightweightTheme();
super.onCreate(savedInstanceState);
if (mIsAbortingAppLaunch) {
return;
}
+ mOnboardingHelper = new OnboardingHelper(this, safeStartingIntent);
initSwitchboardAndMma(this, safeStartingIntent, isInAutomation);
initTelemetryUploader(isInAutomation);
mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
mActionBarFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
mVideoPlayer = (VideoPlayer) findViewById(R.id.video_player);
@@ -1009,24 +1003,26 @@ public class BrowserApp extends GeckoApp
return;
} else if (!AppConstants.MOZ_SWITCHBOARD) {
Log.d(LOGTAG, "Switchboard compile-time disabled");
return;
}
final String serverExtra = intent.getStringExtra(INTENT_KEY_SWITCHBOARD_SERVER);
final String serverUrl = TextUtils.isEmpty(serverExtra) ? SWITCHBOARD_SERVER : serverExtra;
- new AsyncConfigLoader(context, serverUrl) {
+ final SwitchBoard.ConfigStatusListener configStatuslistener = mOnboardingHelper;
+ final MmaDelegate.MmaVariablesChangedListener variablesChangedListener = mOnboardingHelper;
+ new AsyncConfigLoader(context, serverUrl, configStatuslistener) {
@Override
protected Void doInBackground(Void... params) {
super.doInBackground(params);
- SwitchBoard.loadConfig(context, serverUrl);
+ SwitchBoard.loadConfig(context, serverUrl, configStatuslistener);
if (GeckoPreferences.isMmaAvailableAndEnabled(context)) {
// Do LeanPlum start/init here
- MmaDelegate.init(BrowserApp.this);
+ MmaDelegate.init(BrowserApp.this, variablesChangedListener);
}
return null;
}
}.execute();
}
private static void initTelemetryUploader(final boolean isInAutomation) {
TelemetryUploadService.setDisabled(isInAutomation);
@@ -1084,116 +1080,16 @@ public class BrowserApp extends GeckoApp
.putBoolean(EOL_NOTIFIED, true)
.apply();
}
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
- /**
- * Code to actually show the first run pager, separated
- * for distribution purposes.
- */
- @UiThread
- private void checkFirstrunInternal() {
- showFirstrunPager();
-
- if (HardwareUtils.isTablet()) {
- mTabStrip.setOnTabChangedListener(new TabStripInterface.OnTabAddedOrRemovedListener() {
- @Override
- public void onTabChanged() {
- hideFirstrunPager(TelemetryContract.Method.BUTTON);
- mTabStrip.setOnTabChangedListener(null);
- }
- });
- }
- }
-
- /**
- * Check and show the firstrun pane if the browser has never been launched and
- * is not opening an external link from another application.
- *
- * @param context Context of application; used to show firstrun pane if appropriate
- * @param intent Intent that launched this activity
- */
- private void checkFirstrun(Context context, SafeIntent intent) {
- if (getProfile().inGuestMode()) {
- // We do not want to show any first run tour for guest profiles.
- return;
- }
-
- if (intent.getBooleanExtra(EXTRA_SKIP_STARTPANE, false)) {
- // Note that we don't set the pref, so subsequent launches can result
- // in the firstrun pane being shown.
- return;
- }
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
-
- try {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
-
- if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED_OLD, true) &&
- prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
- showSplashScreen = false;
- if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
- // Check to see if a distribution has turned off the first run pager.
- final Distribution distribution = Distribution.getInstance(BrowserApp.this);
- if (!distribution.shouldWaitForSystemDistribution()) {
- checkFirstrunInternal();
- } else {
- distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
- @Override
- public void distributionNotFound() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- checkFirstrunInternal();
- }
- });
- }
-
- @Override
- public void distributionFound(final Distribution distribution) {
- // Check preference again in case distribution turned it off.
- if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- checkFirstrunInternal();
- }
- });
- }
- }
-
- @Override
- public void distributionArrivedLate(final Distribution distribution) {
- }
- });
- }
- }
-
- prefs.edit()
- // Don't bother trying again to show the v1 minimal first run.
- .putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, false)
- // Generate a unique identify for the current first run.
- // See Bug 1429735 for why we care to do this.
- .putString(FIRSTRUN_UUID, UUID.randomUUID().toString())
- .apply();
-
- // We have no intention of stopping this session. The FIRSTRUN session
- // ends when the browsing session/activity has ended. All events
- // during firstrun will be tagged as FIRSTRUN.
- Telemetry.startUISession(TelemetryContract.Session.FIRSTRUN);
- }
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
-
private Class<?> getMediaPlayerManager() {
if (AppConstants.MOZ_MEDIA_PLAYER) {
try {
return Class.forName("org.mozilla.gecko.MediaPlayerManager");
} catch (Exception ex) {
// Ignore failures
Log.e(LOGTAG, "No native casting support", ex);
}
@@ -1241,17 +1137,17 @@ public class BrowserApp extends GeckoApp
}
@Override
public void onAttachedToWindow() {
final SafeIntent intent = new SafeIntent(getIntent());
if (!IntentUtils.getIsInAutomationFromEnvironment(intent)) {
// We can't show the first run experience until Gecko has finished initialization (bug 1077583).
- checkFirstrun(this, intent);
+ mOnboardingHelper.checkFirstRun();
}
}
@Override
protected void processTabQueue() {
if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
@@ -2655,19 +2551,20 @@ public class BrowserApp extends GeckoApp
mBrowserToolbar.cancelEdit();
}
private boolean isHomePagerVisible() {
return (mHomeScreen != null && mHomeScreen.isVisible()
&& mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
}
- private boolean isFirstrunVisible() {
- return (mFirstrunAnimationContainer != null && mFirstrunAnimationContainer.isVisible()
- && mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
+ private SplashScreen getSplashScreen() {
+ final ViewGroup main = (ViewGroup) findViewById(R.id.gecko_layout);
+ final View splashLayout = LayoutInflater.from(this).inflate(R.layout.splash_screen, main);
+ return (SplashScreen) splashLayout.findViewById(R.id.splash_root);
}
/**
* Enters editing mode with the current tab's URL. There might be no
* tabs loaded by the time the user enters editing mode e.g. just after
* the app starts. In this case, we simply fallback to an empty URL.
*/
private void enterEditingMode() {
@@ -2910,18 +2807,21 @@ public class BrowserApp extends GeckoApp
return;
}
// History will only store that we were visiting about:home, however the specific panel
// isn't stored. (We are able to navigate directly to homepanels using an about:home?panel=...
// URL, but the reverse doesn't apply: manually switching panels doesn't update the URL.)
// Hence we need to restore the panel, in addition to panel state, here.
if (isAboutHome(tab)) {
- // For some reason(e.g. from SearchWidget) we are showing the splash schreen. We should hide it now.
- if (splashScreen != null && splashScreen.getVisibility() == View.VISIBLE) {
+ // For some reason(e.g. from SearchWidget) we are showing the splash schreen.
+ // If we are not waiting for the onboarding screens we should hide it now.
+ if (!mOnboardingHelper.isPreparing() &&
+ splashScreen != null &&
+ splashScreen.getVisibility() == View.VISIBLE) {
// Below line will be run when LOCATION_CHANGE. Which means the page load is almost completed.
splashScreen.hide();
}
String panelId = AboutPages.getPanelIdFromAboutHomeUrl(tab.getURL());
Bundle panelRestoreData = null;
if (panelId == null) {
// No panel was specified in the URL. Try loading the most recent
@@ -2950,20 +2850,19 @@ public class BrowserApp extends GeckoApp
}
showSplashScreen = false;
} else {
// The tab going to load is not about page. It's a web page.
// If showSplashScreen is true, it means the app is first launched. We want to show the SlashScreen
// But if GeckoThread.isRunning, the will be 0 sec for web rendering.
// In that case, we don't want to show the SlashScreen/
if (showSplashScreen && !GeckoThread.isRunning()) {
-
- final ViewGroup main = (ViewGroup) findViewById(R.id.gecko_layout);
- final View splashLayout = LayoutInflater.from(this).inflate(R.layout.splash_screen, main);
- splashScreen = (SplashScreen) splashLayout.findViewById(R.id.splash_root);
+ if (splashScreen == null) {
+ splashScreen = getSplashScreen();
+ }
showSplashScreen = false;
} else if (splashScreen != null) {
// Below line will be run when LOCATION_CHANGE. Which means the page load is almost completed.
splashScreen.hide();
}
hideHomePager();
}
@@ -3018,36 +2917,16 @@ public class BrowserApp extends GeckoApp
for (final BrowserAppDelegate delegate : delegates) {
delegate.onActivityResult(this, requestCode, resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
}
}
- private void showFirstrunPager() {
-
- if (mFirstrunAnimationContainer == null) {
- final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
- mFirstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
- mFirstrunAnimationContainer.load(getApplicationContext(), getSupportFragmentManager());
- mFirstrunAnimationContainer.registerOnFinishListener(new FirstrunAnimationContainer.OnFinishListener() {
- @Override
- public void onFinish() {
- if (mFirstrunAnimationContainer.showBrowserHint() &&
- !Tabs.hasHomepage(BrowserApp.this)) {
- enterEditingMode();
- }
- }
- });
- }
-
- mHomeScreenContainer.setVisibility(View.VISIBLE);
- }
-
private void showHomePager(String panelId, Bundle panelRestoreData) {
showHomePagerWithAnimator(panelId, panelRestoreData, null);
}
private void showHomePagerWithAnimator(String panelId, Bundle panelRestoreData, PropertyAnimator animator) {
if (isHomePagerVisible()) {
// Home pager already visible, make sure it shows the correct panel.
mHomeScreen.showPanel(panelId, panelRestoreData);
@@ -3169,25 +3048,22 @@ public class BrowserApp extends GeckoApp
}
/**
* Hide the Onboarding pager on user action, and don't show any onFinish hints.
* @param method TelemetryContract method by which action was taken
* @return boolean of whether pager was visible
*/
private boolean hideFirstrunPager(TelemetryContract.Method method) {
- if (!isFirstrunVisible()) {
+ if (!mOnboardingHelper.hideOnboarding()) {
return false;
}
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, method, "firstrun-pane");
- // Don't show any onFinish actions when hiding from this Activity.
- mFirstrunAnimationContainer.registerOnFinishListener(null);
- mFirstrunAnimationContainer.hide();
return true;
}
/**
* Hides the HomePager, using the url of the currently selected tab as the url to be
* loaded.
*/
private void hideHomePager() {
@@ -4629,9 +4505,40 @@ public class BrowserApp extends GeckoApp
public void onLightweightThemeReset() {
refreshStatusBarColor();
}
private void refreshStatusBarColor() {
final boolean isPrivate = mBrowserToolbar.isPrivateMode();
WindowUtil.setStatusBarColor(BrowserApp.this, isPrivate);
}
+
+ @Override
+ public void onOnboardingProcessStarted() {
+ if (splashScreen == null) {
+ splashScreen = getSplashScreen();
+ }
+
+ splashScreen.show(OnboardingHelper.DELAY_SHOW_DEFAULT_ONBOARDING);
+ }
+
+ @Override
+ public void onOnboardingScreensVisible() {
+ mHomeScreenContainer.setVisibility(View.VISIBLE);
+
+ if (HardwareUtils.isTablet()) {
+ mTabStrip.setOnTabChangedListener(new BrowserApp.TabStripInterface.OnTabAddedOrRemovedListener() {
+ @Override
+ public void onTabChanged() {
+ hideFirstrunPager(TelemetryContract.Method.BUTTON);
+ mTabStrip.setOnTabChangedListener(null);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onFinishedOnboarding(final boolean showBrowserHint) {
+ if (showBrowserHint && !Tabs.hasHomepage(this)) {
+ enterEditingMode();
+ }
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -141,17 +141,17 @@ public abstract class GeckoApp extends G
public static final String PREFS_CLEANUP_TEMP_FILES = "cleanupTempFiles";
/**
* Used with SharedPreferences, per profile, to determine if this is the first run of
* the application. When accessing SharedPreferences, the default value of true should be used.
* Originally, this was only used for the telemetry core ping logic. To avoid
* having to write custom migration logic, we just keep the original pref key.
- * Be aware of {@link org.mozilla.gecko.fxa.EnvironmentUtils.GECKO_PREFS_IS_FIRST_RUN}.
+ * Be aware of {@link org.mozilla.gecko.fxa.EnvironmentUtils#GECKO_PREFS_IS_FIRST_RUN}.
*/
public static final String PREFS_IS_FIRST_RUN = "telemetry-isFirstRun";
public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
// Delay before running one-time "cleanup" tasks that may be needed
// after a version upgrade.
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstRunPanelConfigProviderStrategy.java
@@ -0,0 +1,13 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+public interface FirstRunPanelConfigProviderStrategy {
+ PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, final boolean useLocalValues);
+}
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java
@@ -10,56 +10,59 @@ import android.support.v4.app.FragmentMa
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.mma.MmaDelegate;
import org.mozilla.gecko.preferences.GeckoPreferences;
/**
* A container for the pager and the entire first run experience.
* This is used for animation purposes.
*/
public class FirstrunAnimationContainer extends LinearLayout {
// See bug 1330714. Need NON_PREF_PREFIX to set from distribution.
public static final String PREF_FIRSTRUN_ENABLED_OLD = "startpane_enabled";
// After 57, the pref name will be changed. Thus all user since 57 will check this new pref.
public static final String PREF_FIRSTRUN_ENABLED = GeckoPreferences.NON_PREF_PREFIX + "startpane_enabled_after_57";
- public static interface OnFinishListener {
- public void onFinish();
+ public interface OnFinishListener {
+ void onFinish();
}
private FirstrunPager pager;
private boolean visible;
private OnFinishListener onFinishListener;
public FirstrunAnimationContainer(Context context) {
this(context, null);
}
public FirstrunAnimationContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public void load(Context appContext, FragmentManager fm) {
+ public void load(Context appContext, FragmentManager fm, final boolean useLocalValues) {
visible = true;
- pager = (FirstrunPager) findViewById(R.id.firstrun_pager);
- pager.load(appContext, fm, new OnFinishListener() {
+ pager = findViewById(R.id.firstrun_pager);
+ pager.load(appContext, fm, useLocalValues, new OnFinishListener() {
@Override
public void onFinish() {
hide();
}
});
+
+ if (useLocalValues) {
+ MmaDelegate.track(MmaDelegate.ONBOARDING_DEFAULT_VALUES);
+ } else {
+ MmaDelegate.track(MmaDelegate.ONBOARDING_REMOTE_VALUES);
+ }
}
public boolean isVisible() {
return visible;
}
public void hide() {
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
@@ -58,25 +58,26 @@ public class FirstrunPager extends RtlVi
setCurrentItem(index, true);
}
});
}
super.addView(child, index, params);
}
- public void load(Context appContext, FragmentManager fm, final FirstrunAnimationContainer.OnFinishListener onFinishListener) {
+ public void load(Context appContext, FragmentManager fm, final boolean useLocalValues,
+ final FirstrunAnimationContainer.OnFinishListener onFinishListener) {
final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
- if (Restrictions.isRestrictedProfile(context)) {
- panels = FirstrunPagerConfig.getRestricted();
- } else if (FirefoxAccounts.firefoxAccountsExist(context)) {
- panels = FirstrunPagerConfig.forFxAUser(appContext);
+ if (Restrictions.isRestrictedProfile(appContext)) {
+ panels = FirstrunPagerConfig.getRestricted(appContext);
+ } else if (FirefoxAccounts.firefoxAccountsExist(appContext)) {
+ panels = FirstrunPagerConfig.forFxAUser(appContext, useLocalValues);
} else {
- panels = FirstrunPagerConfig.getDefault(appContext);
+ panels = FirstrunPagerConfig.getDefault(appContext, useLocalValues);
}
setAdapter(new ViewPagerAdapter(fm, panels));
this.pagerNavigation = new FirstrunPanel.PagerNavigation() {
@Override
public void next() {
final int currentPage = FirstrunPager.this.getCurrentItem();
if (currentPage < FirstrunPager.this.getAdapter().getCount() - 1) {
@@ -139,17 +140,17 @@ public class FirstrunPager extends RtlVi
private final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
private final Fragment[] fragments;
public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanelConfig> panels) {
super(fm);
this.panels = panels;
this.fragments = new Fragment[panels.size()];
for (FirstrunPagerConfig.FirstrunPanelConfig panel : panels) {
- mDecor.onAddPagerView(context.getString(panel.getTitleRes()));
+ mDecor.onAddPagerView(panel.getTitle());
}
if (panels.size() > 0) {
mDecor.onPageSelected(0);
}
}
@Override
@@ -167,12 +168,12 @@ public class FirstrunPager extends RtlVi
@Override
public int getCount() {
return panels.size();
}
@Override
public CharSequence getPageTitle(int i) {
// Unused now that we use TabMenuStrip.
- return context.getString(panels.get(i).getTitleRes()).toUpperCase();
+ return panels.get(i).getTitle().toUpperCase();
}
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java
@@ -1,100 +1,99 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.firstrun;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.Bundle;
-import android.util.Log;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.Experiments;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.mma.MmaDelegate;
import java.util.LinkedList;
import java.util.List;
-public class FirstrunPagerConfig {
- public static final String LOGTAG = "FirstrunPagerConfig";
+class FirstrunPagerConfig {
+ static final String LOGTAG = "FirstrunPagerConfig";
- public static final String KEY_IMAGE = "imageRes";
- public static final String KEY_TEXT = "textRes";
- public static final String KEY_SUBTEXT = "subtextRes";
+ static final String KEY_IMAGE = "panelImage";
+ static final String KEY_MESSAGE = "panelMessage";
+ static final String KEY_SUBTEXT = "panelDescription";
- public static List<FirstrunPanelConfig> getDefault(Context context) {
+ static List<FirstrunPanelConfig> getDefault(Context context, final boolean useLocalValues) {
final List<FirstrunPanelConfig> panels = new LinkedList<>();
- panels.add(SimplePanelConfigs.welcomePanelConfig);
- panels.add(SimplePanelConfigs.privatePanelConfig);
- panels.add(SimplePanelConfigs.customizePanelConfig);
- panels.add(SimplePanelConfigs.syncPanelConfig);
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.WELCOME, useLocalValues));
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.PRIVACY, useLocalValues));
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.CUSTOMIZE, useLocalValues));
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.SYNC, useLocalValues));
return panels;
}
- public static List<FirstrunPanelConfig> forFxAUser(Context context) {
+ static List<FirstrunPanelConfig> forFxAUser(Context context, final boolean useLocalValues) {
final List<FirstrunPanelConfig> panels = new LinkedList<>();
- panels.add(SimplePanelConfigs.welcomePanelConfig);
- panels.add(SimplePanelConfigs.privatePanelConfig);
- panels.add(SimplePanelConfigs.customizeLastPanelConfig);
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.WELCOME, useLocalValues));
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.PRIVACY, useLocalValues));
+ panels.add(FirstrunPanelConfig.getConfiguredPanel(context, PanelConfig.TYPE.LAST_CUSTOMIZE, useLocalValues));
return panels;
}
- public static List<FirstrunPanelConfig> getRestricted() {
+ static List<FirstrunPanelConfig> getRestricted(Context context) {
final List<FirstrunPanelConfig> panels = new LinkedList<>();
- panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
+ panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(),
+ context.getString(RestrictedWelcomePanel.TITLE_RES)));
return panels;
}
- public static class FirstrunPanelConfig {
-
+ static class FirstrunPanelConfig {
private String classname;
- private int titleRes;
+ private String title;
private Bundle args;
- public FirstrunPanelConfig(String resource, int titleRes) {
- this(resource, titleRes, -1, -1, -1, true);
+ FirstrunPanelConfig(String resource, String title) {
+ this(resource, title, null, null, null, true);
}
- public FirstrunPanelConfig(String classname, int titleRes, int imageRes, int textRes, int subtextRes) {
- this(classname, titleRes, imageRes, textRes, subtextRes, false);
- }
-
- private FirstrunPanelConfig(String classname, int titleRes, int imageRes, int textRes, int subtextRes, boolean isCustom) {
+ private FirstrunPanelConfig(String classname, String title, Bitmap image, String message,
+ String subtext, boolean isCustom) {
this.classname = classname;
- this.titleRes = titleRes;
+ this.title = title;
if (!isCustom) {
- this.args = new Bundle();
- this.args.putInt(KEY_IMAGE, imageRes);
- this.args.putInt(KEY_TEXT, textRes);
- this.args.putInt(KEY_SUBTEXT, subtextRes);
+ args = new Bundle();
+ args.putParcelable(KEY_IMAGE, image);
+ args.putString(KEY_MESSAGE, message);
+ args.putString(KEY_SUBTEXT, subtext);
}
}
- public String getClassname() {
- return this.classname;
+ static FirstrunPanelConfig getConfiguredPanel(@NonNull Context context,
+ PanelConfig.TYPE wantedPanelConfig,
+ final boolean useLocalValues) {
+ PanelConfig panelConfig;
+ if (useLocalValues) {
+ panelConfig = new LocalFirstRunPanelProvider().getPanelConfig(context, wantedPanelConfig, useLocalValues);
+ } else {
+ panelConfig = new RemoteFirstRunPanelConfig().getPanelConfig(context, wantedPanelConfig, useLocalValues);
+ }
+ return new FirstrunPanelConfig(panelConfig.getClassName(), panelConfig.getTitle(),
+ panelConfig.getImage(), panelConfig.getMessage(), panelConfig.getText(), false);
}
- public int getTitleRes() {
- return this.titleRes;
+
+ String getClassname() {
+ return classname;
}
- public Bundle getArgs() {
+ String getTitle() {
+ return title;
+ }
+
+ Bundle getArgs() {
return args;
}
}
-
- private static class SimplePanelConfigs {
- public static final FirstrunPanelConfig welcomePanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_panel_title_welcome, R.drawable.firstrun_welcome, R.string.firstrun_urlbar_message, R.string.firstrun_urlbar_subtext);
- public static final FirstrunPanelConfig privatePanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_panel_title_privacy, R.drawable.firstrun_private, R.string.firstrun_privacy_message, R.string.firstrun_privacy_subtext);
- public static final FirstrunPanelConfig customizePanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_panel_title_customize, R.drawable.firstrun_data, R.string.firstrun_customize_message, R.string.firstrun_customize_subtext);
- public static final FirstrunPanelConfig customizeLastPanelConfig = new FirstrunPanelConfig(LastPanel.class.getName(), R.string.firstrun_panel_title_customize, R.drawable.firstrun_data, R.string.firstrun_customize_message, R.string.firstrun_customize_subtext);
-
- public static final FirstrunPanelConfig syncPanelConfig = new FirstrunPanelConfig(SyncPanel.class.getName(), R.string.firstrun_sync_title, R.drawable.firstrun_sync, R.string.firstrun_sync_message, R.string.firstrun_sync_subtext);
-
- }
}
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java
@@ -1,49 +1,50 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.firstrun;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
/**
* Base class for our first run pages. We call these FirstrunPanel for consistency
* with HomePager/HomePanel.
*
* @see FirstrunPager for the containing pager.
*/
public class FirstrunPanel extends Fragment {
- public static final int TITLE_RES = -1;
protected boolean showBrowserHint = true;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_basepanel_checkable_fragment, container, false);
final Bundle args = getArguments();
if (args != null) {
- final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
- final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
- final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
+ final Bitmap image = args.getParcelable(FirstrunPagerConfig.KEY_IMAGE);
+ final String message = args.getString(FirstrunPagerConfig.KEY_MESSAGE);
+ final String subtext = args.getString(FirstrunPagerConfig.KEY_SUBTEXT);
- ((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
- ((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
- ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtextRes);
+ ((ImageView) root.findViewById(R.id.firstrun_image)).setImageBitmap(image);
+ ((TextView) root.findViewById(R.id.firstrun_text)).setText(message);
+ ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtext);
}
root.findViewById(R.id.firstrun_link).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-next");
pagerNavigation.next();
}
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/LastPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/LastPanel.java
@@ -1,15 +1,16 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.firstrun;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
@@ -17,31 +18,29 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
public class LastPanel extends FirstrunPanel {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_basepanel_checkable_fragment, container, false);
final Bundle args = getArguments();
if (args != null) {
- final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
- final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
- final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
+ final Bitmap image = args.getParcelable(FirstrunPagerConfig.KEY_IMAGE);
+ final String message = args.getString(FirstrunPagerConfig.KEY_MESSAGE);
+ final String subtext = args.getString(FirstrunPagerConfig.KEY_SUBTEXT);
- ((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
- ((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
- ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtextRes);
+ ((ImageView) root.findViewById(R.id.firstrun_image)).setImageBitmap(image);
+ ((TextView) root.findViewById(R.id.firstrun_text)).setText(message);
+ ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtext);
((TextView) root.findViewById(R.id.firstrun_link)).setText(R.string.firstrun_welcome_button_browser);
-
}
root.findViewById(R.id.firstrun_link).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-next");
close();
}
});
-
return root;
}
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/LocalFirstRunPanelProvider.java
@@ -0,0 +1,47 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.R;
+
+public class LocalFirstRunPanelProvider implements FirstRunPanelConfigProviderStrategy {
+ public PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE type, final boolean useLocalValues) {
+ final Resources resources = context.getResources();
+ switch (type) {
+ case WELCOME:
+ return new PanelConfig(type, useLocalValues, resources.getString(R.string.firstrun_panel_title_welcome),
+ resources.getString(R.string.firstrun_urlbar_message),
+ resources.getString(R.string.firstrun_urlbar_subtext),
+ BitmapFactory.decodeResource(resources, R.drawable.firstrun_welcome));
+ case PRIVACY:
+ return new PanelConfig(type, useLocalValues, resources.getString(R.string.firstrun_panel_title_privacy),
+ resources.getString(R.string.firstrun_privacy_message),
+ resources.getString(R.string.firstrun_privacy_subtext),
+ BitmapFactory.decodeResource(resources, R.drawable.firstrun_private));
+ case CUSTOMIZE:
+ case LAST_CUSTOMIZE:
+ return new PanelConfig(type, useLocalValues, resources.getString(R.string.firstrun_panel_title_customize),
+ resources.getString(R.string.firstrun_customize_message),
+ resources.getString(R.string.firstrun_customize_subtext),
+ BitmapFactory.decodeResource(resources, R.drawable.firstrun_data));
+ case SYNC:
+ return new PanelConfig(type, useLocalValues, resources.getString(R.string.firstrun_sync_title),
+ resources.getString(R.string.firstrun_sync_message),
+ resources.getString(R.string.firstrun_sync_subtext),
+ BitmapFactory.decodeResource(resources, R.drawable.firstrun_sync));
+ default: // This will also be the case for "WELCOME"
+ return new PanelConfig(type, useLocalValues, resources.getString(R.string.firstrun_panel_title_welcome),
+ resources.getString(R.string.firstrun_urlbar_message),
+ resources.getString(R.string.firstrun_urlbar_subtext),
+ BitmapFactory.decodeResource(resources, R.drawable.firstrun_welcome));
+ }
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/OnboardingHelper.java
@@ -0,0 +1,354 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.StrictMode;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.ViewStub;
+
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.distribution.Distribution;
+import org.mozilla.gecko.mma.MmaDelegate;
+import org.mozilla.gecko.mozglue.SafeIntent;
+import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.mozilla.gecko.util.NetworkUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * Helper class of an an {@link AppCompatActivity} for managing showing the Onboarding screens.
+ * <br>The user class will have to implement {@link OnboardingListener}.
+ */
+public class OnboardingHelper implements MmaDelegate.MmaVariablesChangedListener,
+ SwitchBoard.ConfigStatusListener {
+ private static final String LOGTAG = "OnboardingHelper";
+ private static final boolean DEBUG = false;
+
+ @RobocopTarget
+ public static final String EXTRA_SKIP_STARTPANE = "skipstartpane";
+
+ /** Be aware of {@link org.mozilla.gecko.fxa.EnvironmentUtils#GECKO_PREFS_FIRSTRUN_UUID}. */
+ private static final String FIRSTRUN_UUID = "firstrun_uuid";
+
+ // Speculative timeout for showing the Onboarding screens with the default local values.
+ public static final int DELAY_SHOW_DEFAULT_ONBOARDING = 3 * 1000;
+
+ private WeakReference<AppCompatActivity> activityRef;
+ private OnboardingListener listener;
+ private SafeIntent activityStartingIntent;
+ private FirstrunAnimationContainer firstrunAnimationContainer;
+ private Runnable showOnboarding;
+ private boolean onboardingIsPreparing;
+ private boolean abortOnboarding;
+ private long startTimeForCheckingOnlineVariables;
+
+ public OnboardingHelper(
+ @NonNull final AppCompatActivity activity,
+ @NonNull final SafeIntent activityStartingIntent)
+ throws IllegalArgumentException {
+
+ if (!(activity instanceof OnboardingListener)) {
+ final String activityClass = activity.getClass().getSimpleName();
+ final String listenerInterface = OnboardingListener.class.getSimpleName();
+ throw new IllegalArgumentException(
+ String.format("%s does not implement %s", activityClass, listenerInterface));
+ }
+
+ this.activityRef = new WeakReference<>(activity);
+ this.listener = (OnboardingListener) activity;
+ this.activityStartingIntent = activityStartingIntent;
+ }
+
+ /**
+ * Check and show the firstrun pane if the browser has never been launched and
+ * is not opening an external link from another application.
+ */
+ public void checkFirstRun() {
+ if (GeckoThread.getActiveProfile().inGuestMode()) {
+ // We do not want to show any first run tour for guest profiles.
+ return;
+ }
+
+ if (activityStartingIntent.getBooleanExtra(EXTRA_SKIP_STARTPANE, false)) {
+ // Note that we don't set the pref, so subsequent launches can result
+ // in the firstrun pane being shown.
+ return;
+ }
+
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+
+ try {
+ AppCompatActivity activity = activityRef.get();
+ if (activity == null) {
+ return;
+ }
+ final SharedPreferences prefs = GeckoSharedPrefs.forProfile(activity);
+
+ if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED_OLD, true) &&
+ prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
+
+ onboardingIsPreparing = true;
+ listener.onOnboardingProcessStarted();
+
+ // Allow the activity to be gc'ed while waiting for the distribution
+ activity = null;
+
+ if (!Intent.ACTION_VIEW.equals(activityStartingIntent.getAction())) {
+ // Check to see if a distribution has turned off the first run pager.
+ final Distribution distribution = Distribution.getInstance(activityRef.get());
+ if (!distribution.shouldWaitForSystemDistribution()) {
+ checkFirstrunInternal();
+ } else {
+ distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
+ @Override
+ public void distributionNotFound() {
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ checkFirstrunInternal();
+ }
+ });
+ }
+
+ @Override
+ public void distributionFound(final Distribution distribution) {
+ // Check preference again in case distribution turned it off.
+ if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ checkFirstrunInternal();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void distributionArrivedLate(final Distribution distribution) {
+ }
+ });
+ }
+ }
+
+ // We have no intention of stopping this session. The FIRSTRUN session
+ // ends when the browsing session/activity has ended. All events
+ // during firstrun will be tagged as FIRSTRUN.
+ Telemetry.startUISession(TelemetryContract.Session.FIRSTRUN);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ }
+
+ /**
+ * Call this to prevent or finish displaying of the Onboarding process.<br>
+ * If it has not yet been shown to the user and now it has been prevented to,
+ * showing the Onboarding screens will be retried at the next app start.
+ *
+ * @return whether Onboarding was prevented / finished early or not.
+ */
+ public boolean hideOnboarding() {
+ abortOnboarding = true;
+
+ if (DEBUG) {
+ Log.d(LOGTAG, "hideOnboarding");
+ }
+
+ if (isPreparing()) {
+ onboardingIsPreparing = false;
+ // Cancel showing Onboarding. Will retry automatically at the next app startup.
+ ThreadUtils.removeCallbacksFromUiThread(showOnboarding);
+ return true;
+ }
+
+ if (isOnboardingVisible()) {
+ onboardingIsPreparing = false;
+ firstrunAnimationContainer.registerOnFinishListener(null);
+ firstrunAnimationContainer.hide();
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isOnboardingVisible() {
+ return firstrunAnimationContainer != null && firstrunAnimationContainer.isVisible();
+ }
+
+ /**
+ * Get if we are in the process of preparing the Onboarding screens.<br>
+ * If the Onboarding screens should be shown to the user, they will be so after a small delay -
+ * up to {@link #DELAY_SHOW_DEFAULT_ONBOARDING} necessary for downloading the data
+ * needed to populate the screens.
+ *
+ * @return <code>true</code> - we are preparing for showing Onboarding but haven't yet done
+ * <code>false</code> - Onboarding has been displayed
+ */
+ public boolean isPreparing() {
+ return onboardingIsPreparing;
+ }
+
+ /**
+ * Code to actually show the first run pager, separated for distribution purposes.<br>
+ * If network is available it will first try to use server values for populating the
+ * onboarding screens. If that isn't possible the default local values will be used.
+ */
+ @UiThread
+ private void checkFirstrunInternal() {
+ final AppCompatActivity activity = activityRef.get();
+ if (activity == null) {
+ return;
+ }
+
+ if (abortOnboarding) {
+ return;
+ }
+
+ if (NetworkUtils.isConnected(activity)) {
+ showOnboarding = new Runnable() {
+ @Override
+ public void run() {
+ showFirstrunPager(true);
+ }
+ };
+
+ if (DEBUG) {
+ startTimeForCheckingOnlineVariables = System.currentTimeMillis();
+ }
+
+ ThreadUtils.postDelayedToUiThread(showOnboarding, DELAY_SHOW_DEFAULT_ONBOARDING);
+ } else {
+ showFirstrunPager(true);
+ }
+ }
+
+ private void showFirstrunPager(final boolean useLocalValues) {
+ final AppCompatActivity activity = activityRef.get();
+ if (activity == null) {
+ return;
+ }
+
+ onboardingIsPreparing = false;
+
+ if (firstrunAnimationContainer == null) {
+ final ViewStub firstrunPagerStub = (ViewStub) activity.findViewById(R.id.firstrun_pager_stub);
+ firstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
+ }
+
+ if (DEBUG) {
+ final StringBuilder logMessage =
+ new StringBuilder("Will show Onboarding using ")
+ .append((useLocalValues ? "local" : "server"))
+ .append(" values");
+ Log.d(LOGTAG, logMessage.toString());
+ }
+
+ firstrunAnimationContainer.load
+ (activity.getApplicationContext(), activity.getSupportFragmentManager(), useLocalValues);
+ firstrunAnimationContainer.registerOnFinishListener(new FirstrunAnimationContainer.OnFinishListener() {
+ @Override
+ public void onFinish() {
+ listener.onFinishedOnboarding(firstrunAnimationContainer.showBrowserHint());
+ }
+ });
+
+ listener.onOnboardingScreensVisible();
+ saveOnboardingShownStatus();
+ }
+
+ // The Onboarding screens should only be shown one time.
+ private void saveOnboardingShownStatus() {
+ // The method is called serially from showFirstrunPager()
+ // which stores a hard reference to the activity so it's safe to use it directly
+ final SharedPreferences prefs = GeckoSharedPrefs.forProfile(activityRef.get());
+
+ prefs.edit()
+ // Don't bother trying again to show the v1 minimal first run.
+ .putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, false)
+ // Generate a unique identifier for the current first run.
+ // See Bug 1429735 for why we care to do this.
+ .putString(FIRSTRUN_UUID, UUID.randomUUID().toString())
+ .apply();
+ }
+
+ /**
+ * Try showing the Onboarding screens even before #DELAY_SHOW_DEFAULT_ONBOARDING.<br>
+ * If they have already been shown calling this method has no effect.
+ */
+ private void tryShowOnboarding(final boolean shouldUseLocalValues) {
+ final AppCompatActivity activity = activityRef.get();
+ if (activity == null) {
+ return;
+ }
+
+ if (isPreparing()) {
+ ThreadUtils.removeCallbacksFromUiThread(showOnboarding);
+ showFirstrunPager(shouldUseLocalValues);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onRemoteVariablesChanged() {
+ if (DEBUG) {
+ final long timeElapsed = System.currentTimeMillis() - startTimeForCheckingOnlineVariables;
+ Log.d(LOGTAG, String.format("Got online variables after: %d millis", timeElapsed));
+ }
+
+ tryShowOnboarding(false);
+ }
+
+ @Override
+ @MainThread
+ public void onRemoteVariablesUnavailable() {
+ tryShowOnboarding(true);
+ }
+
+ @Override
+ @MainThread
+ public void onExperimentsConfigLoaded() {
+ final AppCompatActivity activity = activityRef.get();
+ if (activity == null) {
+ return;
+ }
+
+ // Only if the Mma experiment is available we should continue to wait for server values.
+ if (!MmaDelegate.isMmaExperimentEnabled(activity)) {
+ tryShowOnboarding(true);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onExperimentsConfigLoadFailed() {
+ tryShowOnboarding(true);
+ }
+
+ /**
+ * Informs about the status of the onboarding process.
+ */
+ public interface OnboardingListener {
+
+ void onOnboardingProcessStarted();
+
+ void onOnboardingScreensVisible();
+
+ void onFinishedOnboarding(final boolean showBrowserHint);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/PanelConfig.java
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.graphics.Bitmap;
+
+/**
+ * Onboarding screens configuration object.
+ */
+public class PanelConfig {
+ public enum TYPE {
+ WELCOME, PRIVACY, CUSTOMIZE, LAST_CUSTOMIZE, SYNC
+ }
+
+ private final TYPE type;
+ private final boolean useLocalValues;
+ private final String title;
+ private final String message;
+ private final String text;
+ private final Bitmap image;
+
+ public PanelConfig(TYPE type, boolean useLocalValues, String title, String message, String text, Bitmap image) {
+ this.type = type;
+ this.useLocalValues = useLocalValues;
+ this.title = title;
+ this.message = message;
+ this.text = text;
+ this.image = image;
+ }
+
+ public String getClassName() {
+ switch (type) {
+ case WELCOME:
+ case PRIVACY:
+ case CUSTOMIZE:
+ return FirstrunPanel.class.getName();
+ case LAST_CUSTOMIZE:
+ return LastPanel.class.getName();
+ case SYNC:
+ return SyncPanel.class.getName();
+ default: // Return the default Panel, same as for "WELCOME"
+ return FirstrunPanel.class.getName();
+ }
+ }
+
+ public TYPE getType() {
+ return type;
+ }
+
+ public boolean isUsingLocalValues() {
+ return useLocalValues;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Bitmap getImage() {
+ return image;
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/RemoteFirstRunPanelConfig.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.firstrun;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.mma.MmaDelegate;
+
+public class RemoteFirstRunPanelConfig implements FirstRunPanelConfigProviderStrategy {
+ @Override
+ public PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE type, final boolean useLocalValues) {
+ return MmaDelegate.getPanelConfig(context, type, useLocalValues);
+ }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java
@@ -1,16 +1,17 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.firstrun;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
@@ -19,23 +20,23 @@ import org.mozilla.gecko.fxa.FxAccountCo
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
public class SyncPanel extends FirstrunPanel {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_sync_fragment, container, false);
final Bundle args = getArguments();
if (args != null) {
- final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
- final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
- final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
+ final Bitmap image = args.getParcelable(FirstrunPagerConfig.KEY_IMAGE);
+ final String message = args.getString(FirstrunPagerConfig.KEY_MESSAGE);
+ final String subtext = args.getString(FirstrunPagerConfig.KEY_SUBTEXT);
- ((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
- ((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
- ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtextRes);
+ ((ImageView) root.findViewById(R.id.firstrun_image)).setImageBitmap(image);
+ ((TextView) root.findViewById(R.id.firstrun_text)).setText(message);
+ ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtext);
}
root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
showBrowserHint = false;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/LeanplumVariables.java
@@ -0,0 +1,122 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.mma;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+
+import com.leanplum.annotations.Variable;
+
+import org.mozilla.gecko.R;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unified repo for all LeanPlum variables.<br>
+ * <ul>To make them appear in the LP dashboard and get new values from the server
+ * <li>they must be annotated with {@link com.leanplum.annotations.Variable}.</li>
+ * <li>they need to be parsed with {@link com.leanplum.annotations.Parser} after {@link com.leanplum.Leanplum#setApplicationContext(Context)}</li>
+ * </ul>
+ * Although some fields are public (LP SDK limitation) they are not to be written into.
+ *
+ * @see <a href="https://docs.leanplum.com/reference#defining-variables">Official LP variables documentation</a>
+ */
+public class LeanplumVariables {
+ private static LeanplumVariables INSTANCE;
+ private static Resources appResources;
+
+ private static final String FIRSTRUN_WELCOME_PANEL_GROUP_NAME = "FirstRun Welcome Panel";
+ private static final String FIRSTRUN_PRIVACY_PANEL_GROUP_NAME = "FirstRun Privacy Panel";
+ private static final String FIRSTRUN_CUSTOMIZE_PANEL_GROUP_NAME = "FirstRun Customize Panel";
+ private static final String FIRSTRUN_SYNC_PANEL_GROUP_NAME = "FirstRun Sync Panel";
+
+ @Variable(group = FIRSTRUN_WELCOME_PANEL_GROUP_NAME) public static String welcomePanelTitle;
+ @Variable(group = FIRSTRUN_WELCOME_PANEL_GROUP_NAME) public static String welcomePanelMessage;
+ @Variable(group = FIRSTRUN_WELCOME_PANEL_GROUP_NAME) public static String welcomePanelSubtext;
+ @DrawableRes private static int welcomeDrawableId;
+
+ @Variable(group = FIRSTRUN_PRIVACY_PANEL_GROUP_NAME) public static String privacyPanelTitle;
+ @Variable(group = FIRSTRUN_PRIVACY_PANEL_GROUP_NAME) public static String privacyPanelMessage;
+ @Variable(group = FIRSTRUN_PRIVACY_PANEL_GROUP_NAME) public static String privacyPanelSubtext;
+ @DrawableRes private static int privacyDrawableId;
+
+ @Variable(group = FIRSTRUN_CUSTOMIZE_PANEL_GROUP_NAME) public static String customizePanelTitle;
+ @Variable(group = FIRSTRUN_CUSTOMIZE_PANEL_GROUP_NAME) public static String customizePanelMessage;
+ @Variable(group = FIRSTRUN_CUSTOMIZE_PANEL_GROUP_NAME) public static String customizePanelSubtext;
+ @DrawableRes private static int customizingDrawableId;
+
+ @Variable(group = FIRSTRUN_SYNC_PANEL_GROUP_NAME) public static String syncPanelTitle;
+ @Variable(group = FIRSTRUN_SYNC_PANEL_GROUP_NAME) public static String syncPanelMessage;
+ @Variable(group = FIRSTRUN_SYNC_PANEL_GROUP_NAME) public static String syncPanelSubtext;
+ @DrawableRes private static int syncDrawableId;
+
+ /**
+ * Allows constructing and/or returning an already constructed instance of this class
+ * which has all it's fields populated with values from Resources.<br><br>
+ *
+ * An instance of this class needs exist to allow overwriting it's fields with downloaded values from LeanPlum
+ * @see com.leanplum.annotations.Parser#defineFileVariable(Object, String, String, Field)
+ */
+ public static LeanplumVariables getInstance(Context appContext) {
+ // Simple Singleton as we don't expect concurrency problems.
+ if (INSTANCE == null) {
+ INSTANCE = new LeanplumVariables(appContext);
+ }
+
+ return INSTANCE;
+ }
+
+ /**
+ * Allows setting default values for instance variables.
+ * @param context used to access application resources
+ */
+ private LeanplumVariables(@NonNull Context context) {
+ appResources = context.getResources();
+ welcomePanelTitle = appResources.getString(R.string.firstrun_panel_title_welcome);
+ welcomePanelMessage = appResources.getString(R.string.firstrun_urlbar_message);
+ welcomePanelSubtext = appResources.getString(R.string.firstrun_urlbar_subtext);
+ welcomeDrawableId = R.drawable.firstrun_welcome;
+
+ privacyPanelTitle = appResources.getString(R.string.firstrun_panel_title_privacy);
+ privacyPanelMessage = appResources.getString(R.string.firstrun_privacy_message);
+ privacyPanelSubtext = appResources.getString(R.string.firstrun_privacy_subtext);
+ privacyDrawableId = R.drawable.firstrun_private;
+
+ customizePanelTitle = appResources.getString(R.string.firstrun_panel_title_customize);
+ customizePanelMessage = appResources.getString(R.string.firstrun_customize_message);
+ customizePanelSubtext = appResources.getString(R.string.firstrun_customize_subtext);
+ customizingDrawableId = R.drawable.firstrun_data;
+
+ syncPanelTitle = appResources.getString(R.string.firstrun_sync_title);
+ syncPanelMessage = appResources.getString(R.string.firstrun_sync_message);
+ syncPanelSubtext = appResources.getString(R.string.firstrun_sync_subtext);
+ syncDrawableId = R.drawable.firstrun_sync;
+ }
+
+ public static Bitmap getWelcomeImage() {
+ return getBitmapFromMmaVar(welcomeDrawableId);
+ }
+
+ public static Bitmap getPrivacyImage() {
+ return getBitmapFromMmaVar(privacyDrawableId);
+ }
+
+ public static Bitmap getCustomizingImage() {
+ return getBitmapFromMmaVar(customizingDrawableId);
+ }
+
+ public static Bitmap getSyncImage() {
+ return getBitmapFromMmaVar(syncDrawableId);
+ }
+
+ private static Bitmap getBitmapFromMmaVar(@DrawableRes final int drawableRes) {
+ return BitmapFactory.decodeResource(appResources, drawableRes);
+ }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
@@ -19,20 +19,22 @@ import android.text.TextUtils;
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.MmaConstants;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.activitystream.homepanel.ActivityStreamConfiguration;
+import org.mozilla.gecko.firstrun.PanelConfig;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.util.ContextUtils;
+import org.mozilla.gecko.util.ThreadUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MmaDelegate {
@@ -42,16 +44,18 @@ public class MmaDelegate {
public static final String SAVED_BOOKMARK = "E_Saved_Bookmark";
public static final String OPENED_BOOKMARK = "E_Opened_Bookmark";
public static final String INTERACT_WITH_SEARCH_URL_AREA = "E_Interact_With_Search_URL_Area";
public static final String SCREENSHOT = "E_Screenshot";
public static final String SAVED_LOGIN_AND_PASSWORD = "E_Saved_Login_And_Password";
public static final String RESUMED_FROM_BACKGROUND = "E_Resumed_From_Background";
public static final String NEW_TAB = "E_Opened_New_Tab";
public static final String DISMISS_ONBOARDING = "E_Dismiss_Onboarding";
+ public static final String ONBOARDING_DEFAULT_VALUES = "E_Onboarding_With_Default_Values";
+ public static final String ONBOARDING_REMOTE_VALUES = "E_Onboarding_With_Remote_Values";
private static final String LAUNCH_BUT_NOT_DEFAULT_BROWSER = "E_Launch_But_Not_Default_Browser";
private static final String LAUNCH_BROWSER = "E_Launch_Browser";
private static final String CHANGED_DEFAULT_TO_FENNEC = "E_Changed_Default_To_Fennec";
private static final String INSTALLED_FOCUS = "E_Just_Installed_Focus";
private static final String INSTALLED_KLAR = "E_Just_Installed_Klar";
private static final String USER_ATT_FOCUS_INSTALLED = "Focus Installed";
@@ -70,33 +74,40 @@ public class MmaDelegate {
public static final String KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID = "android.not_a_preference.leanplum.device_id";
private static final String KEY_ANDROID_PREF_BOOLEAN_FENNEC_IS_DEFAULT = "android.not_a_preference.fennec.default.browser.status";
private static final String DEBUG_LEANPLUM_DEVICE_ID = "8effda84-99df-11e7-abc4-cec278b6b50a";
private static final MmaInterface mmaHelper = MmaConstants.getMma();
private static Context applicationContext;
- public static void init(Activity activity) {
+ public static void init(final Activity activity,
+ final MmaVariablesChangedListener remoteVariablesListener) {
applicationContext = activity.getApplicationContext();
// Since user attributes are gathered in Fennec, not in MMA implementation,
// we gather the information here then pass to mmaHelper.init()
// Note that generateUserAttribute always return a non null HashMap.
final Map<String, Object> attributes = gatherUserAttributes(activity);
final String deviceId = getDeviceId(activity);
mmaHelper.setDeviceId(deviceId);
PrefsHelper.setPref(GeckoPreferences.PREFS_MMA_DEVICE_ID, deviceId);
// above two config setup required to be invoked before mmaHelper.init.
mmaHelper.init(activity, attributes);
if (!isDefaultBrowser(activity)) {
mmaHelper.event(MmaDelegate.LAUNCH_BUT_NOT_DEFAULT_BROWSER);
}
mmaHelper.event(MmaDelegate.LAUNCH_BROWSER);
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mmaHelper.listenOnceForVariableChanges(remoteVariablesListener);
+ }
+ });
}
public static void stop() {
mmaHelper.stop();
}
/* This method must be called at background thread to avoid performance issues in some API level */
@NonNull
@@ -206,22 +217,32 @@ public class MmaDelegate {
if (isMmaAllowed(context)) {
mmaHelper.setCustomIcon(R.drawable.ic_status_logo);
return mmaHelper.handleGcmMessage(context, from, bundle);
} else {
return false;
}
}
+ public static PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, final boolean useLocalValues) {
+ return mmaHelper.getPanelConfig(context, panelConfigType, useLocalValues);
+ }
+
private static String getDeviceId(Activity activity) {
if (SwitchBoard.isInExperiment(activity, Experiments.LEANPLUM_DEBUG)) {
return DEBUG_LEANPLUM_DEVICE_ID;
}
final SharedPreferences sharedPreferences = activity.getPreferences(Context.MODE_PRIVATE);
String deviceId = sharedPreferences.getString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
if (deviceId == null) {
deviceId = UUID.randomUUID().toString();
sharedPreferences.edit().putString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, deviceId).apply();
}
return deviceId;
}
+
+ public interface MmaVariablesChangedListener {
+ void onRemoteVariablesChanged();
+
+ void onRemoteVariablesUnavailable();
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaInterface.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaInterface.java
@@ -8,16 +8,18 @@ package org.mozilla.gecko.mma;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
+import org.mozilla.gecko.firstrun.PanelConfig;
+
import java.util.Map;
public interface MmaInterface {
void init(Activity Activity, Map<String, ?> attributes);
void setCustomIcon(@DrawableRes int iconResId);
@@ -28,9 +30,13 @@ public interface MmaInterface {
void event(String mmaEvent, double value);
void stop();
@CheckResult boolean handleGcmMessage(Context context, String from, Bundle bundle);
void setDeviceId(@NonNull String deviceId);
+
+ PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, final boolean useLocalValues);
+
+ void listenOnceForVariableChanges(@NonNull final MmaDelegate.MmaVariablesChangedListener listener);
}
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaLeanplumImp.java
@@ -4,35 +4,36 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.mma;
import android.app.Activity;
import android.app.Notification;
import android.content.Context;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import com.leanplum.Leanplum;
import com.leanplum.LeanplumActivityHelper;
import com.leanplum.LeanplumPushNotificationCustomizer;
import com.leanplum.LeanplumPushService;
+import com.leanplum.annotations.Parser;
+import com.leanplum.callbacks.VariablesChangedCallback;
import com.leanplum.internal.Constants;
import com.leanplum.internal.LeanplumInternal;
-import com.leanplum.internal.VarCache;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.MmaConstants;
+import org.mozilla.gecko.firstrun.PanelConfig;
+import java.lang.ref.WeakReference;
import java.util.Map;
-import java.util.UUID;
public class MmaLeanplumImp implements MmaInterface {
@Override
public void init(final Activity activity, Map<String, ?> attributes) {
if (activity == null) {
@@ -42,16 +43,18 @@ public class MmaLeanplumImp implements M
// Need to call this in the eventuality that in this app session stop() has been called.
// It will allow LeanPlum to communicate again with the servers.
Leanplum.setIsTestModeEnabled(false);
Leanplum.setApplicationContext(activity.getApplicationContext());
LeanplumActivityHelper.enableLifecycleCallbacks(activity.getApplication());
+ Parser.parseVariables(LeanplumVariables.getInstance(activity.getApplicationContext()));
+
if (AppConstants.MOZILLA_OFFICIAL) {
Leanplum.setAppIdForProductionMode(MmaConstants.MOZ_LEANPLUM_SDK_CLIENTID, MmaConstants.MOZ_LEANPLUM_SDK_KEY);
} else {
Leanplum.setAppIdForDevelopmentMode(MmaConstants.MOZ_LEANPLUM_SDK_CLIENTID, MmaConstants.MOZ_LEANPLUM_SDK_KEY);
}
LeanplumPushService.setGcmSenderId(AppConstants.MOZ_ANDROID_GCM_SENDERIDS);
@@ -140,9 +143,50 @@ public class MmaLeanplumImp implements M
return false;
}
@Override
public void setDeviceId(@NonNull String deviceId) {
Leanplum.setDeviceId(deviceId);
}
+ @Override
+ public PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE type, final boolean useLocalValues) {
+ if (useLocalValues) {
+ throw new UnsupportedOperationException("Cannot build remote panel config with local values");
+ }
+
+ switch (type) {
+ case WELCOME:
+ return new PanelConfig(type, useLocalValues, LeanplumVariables.welcomePanelTitle, LeanplumVariables.welcomePanelMessage,
+ LeanplumVariables.welcomePanelSubtext, LeanplumVariables.getWelcomeImage());
+ case PRIVACY:
+ return new PanelConfig(type, useLocalValues, LeanplumVariables.privacyPanelTitle, LeanplumVariables.privacyPanelMessage,
+ LeanplumVariables.privacyPanelSubtext, LeanplumVariables.getPrivacyImage());
+ case CUSTOMIZE:
+ case LAST_CUSTOMIZE:
+ return new PanelConfig(type, useLocalValues, LeanplumVariables.customizePanelTitle, LeanplumVariables.customizePanelMessage,
+ LeanplumVariables.customizePanelSubtext, LeanplumVariables.getCustomizingImage());
+ case SYNC:
+ return new PanelConfig(type, useLocalValues, LeanplumVariables.syncPanelTitle, LeanplumVariables.syncPanelMessage,
+ LeanplumVariables.syncPanelSubtext, LeanplumVariables.getSyncImage());
+ default: // This will also be the case for "WELCOME"
+ return new PanelConfig(type, useLocalValues, LeanplumVariables.welcomePanelTitle, LeanplumVariables.welcomePanelMessage,
+ LeanplumVariables.welcomePanelSubtext, LeanplumVariables.getWelcomeImage());
+ }
+ }
+
+ @Override
+ public void listenOnceForVariableChanges(@NonNull final MmaDelegate.MmaVariablesChangedListener listener) {
+ final WeakReference<MmaDelegate.MmaVariablesChangedListener> listenerRef = new WeakReference<>(listener);
+
+ Leanplum.addVariablesChangedHandler(new VariablesChangedCallback() {
+ @Override
+ public void variablesChanged() {
+ Leanplum.removeVariablesChangedHandler(this);
+ MmaDelegate.MmaVariablesChangedListener variablesChangesListener = listenerRef.get();
+ if (variablesChangesListener != null) {
+ variablesChangesListener.onRemoteVariablesChanged();
+ }
+ }
+ });
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaStubImp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaStubImp.java
@@ -7,16 +7,18 @@
package org.mozilla.gecko.mma;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
+import org.mozilla.gecko.firstrun.PanelConfig;
+
import java.util.Map;
public class MmaStubImp implements MmaInterface {
@Override
public void init(Activity activity, Map<String, ?> attributes) {
}
@@ -51,9 +53,18 @@ public class MmaStubImp implements MmaIn
return false;
}
@Override
public void setDeviceId(@NonNull String deviceId) {
}
+ @Override
+ public PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, boolean useLocalValues) {
+ return null;
+ }
+
+ @Override
+ public void listenOnceForVariableChanges(@NonNull MmaDelegate.MmaVariablesChangedListener listener) {
+ listener.onRemoteVariablesUnavailable();
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/switchboard/AsyncConfigLoader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/AsyncConfigLoader.java
@@ -28,27 +28,30 @@ import android.os.AsyncTask;
*
* @author Philipp Berner
*
*/
public class AsyncConfigLoader extends AsyncTask<Void, Void, Void> {
private Context context;
private String defaultServerUrl;
+ private SwitchBoard.ConfigStatusListener listener;
/**
* Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
* or SwitchBoard.loadConfig.
* Loads config with a custom UUID
* @param c Application context
* @param defaultServerUrl Default URL endpoint for Switchboard config.
*/
- public AsyncConfigLoader(Context c, String defaultServerUrl) {
+ public AsyncConfigLoader(Context c, String defaultServerUrl,
+ SwitchBoard.ConfigStatusListener listener) {
this.context = c;
this.defaultServerUrl = defaultServerUrl;
+ this.listener = listener;
}
@Override
protected Void doInBackground(Void... params) {
- SwitchBoard.loadConfig(context, defaultServerUrl);
+ SwitchBoard.loadConfig(context, defaultServerUrl, listener);
return null;
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
@@ -33,16 +33,17 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.search.SearchEngineManager;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.IOUtils;
import org.mozilla.gecko.util.ProxySelector;
+import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
@@ -94,33 +95,52 @@ public class SwitchBoard {
/**
* Loads a new config for a user. This method does network I/O, so it
* should not be called on the main thread.
*
* @param c ApplicationContext
* @param serverUrl Server URL endpoint.
*/
- public static void loadConfig(Context c, @NonNull String serverUrl) {
+ public static void loadConfig(Context c, @NonNull String serverUrl,
+ @NonNull final ConfigStatusListener listener) {
final URL url;
try {
url = new URL(serverUrl);
} catch (MalformedURLException e) {
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ listener.onExperimentsConfigLoadFailed();
+ }
+ });
Log.e(TAG, "Exception creating server URL", e);
return;
}
final String result = readFromUrlGET(url);
if (DEBUG) Log.d(TAG, "Result: " + result);
if (result == null) {
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ listener.onExperimentsConfigLoadFailed();
+ }
+ });
return;
}
// Cache result locally in shared preferences.
Preferences.setDynamicConfigJson(c, result);
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ listener.onExperimentsConfigLoaded();
+ }
+ });
}
public static boolean isInBucket(Context c, int low, int high) {
final int userBucket = getUserBucket(c);
return (userBucket >= low) && (userBucket < high);
}
/**
@@ -448,9 +468,15 @@ public class SwitchBoard {
final DeviceUuidFactory df = new DeviceUuidFactory(c);
final String uuid = df.getDeviceUuid().toString();
CRC32 crc = new CRC32();
crc.update(uuid.getBytes());
long checksum = crc.getValue();
return (int)(checksum % 100L);
}
+
+ public interface ConfigStatusListener {
+ void onExperimentsConfigLoaded();
+
+ void onExperimentsConfigLoadFailed();
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/widget/SplashScreen.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/SplashScreen.java
@@ -34,16 +34,20 @@ public class SplashScreen extends Relati
if (hasReachedThreshold) {
vanish();
} else {
// if the threshold not reached, mark
shouldHideAsap = true;
}
}
+ public void show(final int duration) {
+ atLeast(duration);
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Splash Screen will at least wait for this long before disappear.
atLeast(MIN_DISPLAY_TIME);
}
// the minimum time the splash screen will stay on the screen
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/EnvironmentUtils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/EnvironmentUtils.java
@@ -8,21 +8,21 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.Nullable;
/**
* Provides information about the current environment.
*/
public class EnvironmentUtils {
/**
- * Must be kept in-sync with {@link org.mozilla.gecko.GeckoApp.PREFS_IS_FIRST_RUN}.
+ * Must be kept in-sync with {@link org.mozilla.gecko.GeckoApp#PREFS_IS_FIRST_RUN}.
*/
private static final String GECKO_PREFS_IS_FIRST_RUN = "telemetry-isFirstRun";
/**
- * Must be kept in-sync with {@link org.mozilla.gecko.BrowserApp.FIRSTRUN_UUID}.
+ * Must be kept in-sync with {@link org.mozilla.gecko.firstrun.OnboardingHelper#FIRSTRUN_UUID}.
*/
private static final String GECKO_PREFS_FIRSTRUN_UUID = "firstrun_uuid";
public static boolean isFirstRun(final Context context) {
return getDefaultGeckoSharedPreferences(context).getBoolean(GECKO_PREFS_IS_FIRST_RUN, true);
}
@Nullable
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseRobocopTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseRobocopTest.java
@@ -12,25 +12,24 @@ import android.test.ActivityInstrumentat
import android.text.TextUtils;
import android.util.Log;
import com.robotium.solo.Solo;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Assert;
-import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.Driver;
import org.mozilla.gecko.FennecInstrumentationTestRunner;
import org.mozilla.gecko.FennecMochitestAssert;
import org.mozilla.gecko.FennecNativeActions;
import org.mozilla.gecko.FennecNativeDriver;
import org.mozilla.gecko.FennecTalosAssert;
-import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.firstrun.OnboardingHelper;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
@SuppressWarnings("unchecked")
public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<Activity> {
@@ -121,17 +120,17 @@ public abstract class BaseRobocopTest ex
return BaseRobocopTest.createActivityIntent(mConfig);
}
// Static function to allow re-use.
public static Intent createActivityIntent(Map<String, String> config) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.putExtra("args", "-no-remote -profile " + config.get("profile"));
// Don't show the first run experience.
- intent.putExtra(BrowserApp.EXTRA_SKIP_STARTPANE, true);
+ intent.putExtra(OnboardingHelper.EXTRA_SKIP_STARTPANE, true);
final String envString = config.get("envvars");
if (!TextUtils.isEmpty(envString)) {
final String[] envStrings = envString.split(",");
for (int iter = 0; iter < envStrings.length; iter++) {
intent.putExtra("env" + iter, envStrings[iter]);
}