Bug 1386192 - Test Leanplum Custom Message for Onboarding; r=cnevinchen+582291

Created LeanPlumVariables to allow LeanPlum overwriting the values used for
populating the OnBoarding screens. By simply adding the @Variable annotation
to it's fields, on the first run of the app, they will appear in "LeanPlum
dashboard - Variables" and will allow overwriting for future runs.

The OnBoarding process will now try to use LeanPlum values if possible.
Because connecting to LeanPlum and downloading the Variables might take
a few seconds we use a delay of up to 3 seconds until starting to show
the Onboarding screens.
The default values will still be used if:
- if the LP experiment is not available
- if no internet connection
- if more than 3 seconds have passed and LP didn't finish it's download

Added two new events that could be tracked to Leanplum
MmaDelegate.ONBOARDING_DEFAULT_VALUES and MmaDelegate.ONBOARDING_REMOTE_VALUES
to inform if showing the Onboarding with server values was possible or not.

Because of the 3 seconds delay until showing the Onboarding panels leaking the
could be possible. Used WeakReferences for both the Activity in
OnboardingHelper and the OnboardingHelper in MmaLeanplumImp to avoid it.

MozReview-Commit-ID: H30e9Ng7jrM

--HG--
extra : rebase_source : e403b8010005aa82f8b6440586c533ce99952f9f
This commit is contained in:
Petru Lingurar 2018-07-04 12:56:20 +03:00
Родитель 99a1868412
Коммит b9eed730ee
24 изменённых файлов: 903 добавлений и 251 удалений

Просмотреть файл

@ -145,6 +145,7 @@ android {
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) {

Просмотреть файл

@ -41,7 +41,6 @@ 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;
@ -94,7 +93,7 @@ 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;
@ -162,6 +161,7 @@ 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;
@ -186,7 +186,6 @@ 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;
@ -204,7 +203,8 @@ public class BrowserApp extends GeckoApp
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;
@ -233,15 +233,8 @@ public class BrowserApp extends GeckoApp
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;
@ -254,7 +247,6 @@ public class BrowserApp extends GeckoApp
// 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;
@ -433,6 +425,7 @@ public class BrowserApp extends GeckoApp
@NonNull
private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
private OnboardingHelper mOnboardingHelper; // Contains reference to Context - DO NOT LEAK!
private boolean mHasResumed;
@ -749,6 +742,7 @@ public class BrowserApp extends GeckoApp
return;
}
mOnboardingHelper = new OnboardingHelper(this, safeStartingIntent);
initSwitchboardAndMma(this, safeStartingIntent, isInAutomation);
initTelemetryUploader(isInAutomation);
@ -1014,14 +1008,16 @@ public class BrowserApp extends GeckoApp
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;
}
@ -1089,106 +1085,6 @@ public class BrowserApp extends GeckoApp
}
}
/**
* 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 {
@ -1246,7 +1142,7 @@ public class BrowserApp extends GeckoApp
if (!IntentUtils.getIsInAutomationFromEnvironment(intent)) {
// We can't show the first run experience until Gecko has finished initialization (bug 1077583).
checkFirstrun(this, intent);
mOnboardingHelper.checkFirstRun();
}
}
@ -2660,9 +2556,10 @@ public class BrowserApp extends GeckoApp
&& 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);
}
/**
@ -2915,8 +2812,11 @@ public class BrowserApp extends GeckoApp
// 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();
}
@ -2955,10 +2855,9 @@ public class BrowserApp extends GeckoApp
// 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) {
@ -3023,26 +2922,6 @@ public class BrowserApp extends GeckoApp
}
}
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);
}
@ -3174,15 +3053,12 @@ public class BrowserApp extends GeckoApp
* @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;
}
@ -4634,4 +4510,35 @@ public class BrowserApp extends GeckoApp
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();
}
}
}

Просмотреть файл

@ -146,7 +146,7 @@ public abstract class GeckoApp extends GeckoActivity
* 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";

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -15,9 +15,6 @@ 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;
@ -31,8 +28,8 @@ public class FirstrunAnimationContainer extends LinearLayout {
// 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;
@ -46,15 +43,21 @@ public class FirstrunAnimationContainer extends LinearLayout {
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() {

Просмотреть файл

@ -63,15 +63,16 @@ public class FirstrunPager extends RtlViewPager {
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));
@ -144,7 +145,7 @@ public class FirstrunPager extends RtlViewPager {
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) {
@ -172,7 +173,7 @@ public class FirstrunPager extends RtlViewPager {
@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();
}
}
}

Просмотреть файл

@ -6,95 +6,94 @@
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);
}
}

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.gecko.firstrun;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@ -12,6 +13,7 @@ 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;
@ -24,7 +26,6 @@ import org.mozilla.gecko.TelemetryContract;
*/
public class FirstrunPanel extends Fragment {
public static final int TITLE_RES = -1;
protected boolean showBrowserHint = true;
@Override
@ -32,13 +33,13 @@ public class FirstrunPanel extends Fragment {
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() {

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.gecko.firstrun;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -22,15 +23,14 @@ public class LastPanel extends FirstrunPanel {
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() {
@ -41,7 +41,6 @@ public class LastPanel extends FirstrunPanel {
}
});
return root;
}
}

Просмотреть файл

@ -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));
}
}
}

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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;
}
}

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -6,6 +6,7 @@
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;
@ -24,13 +25,13 @@ public class SyncPanel extends FirstrunPanel {
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() {

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -24,10 +24,12 @@ 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;
@ -47,6 +49,8 @@ public class MmaDelegate {
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";
@ -75,7 +79,8 @@ public class MmaDelegate {
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()
@ -92,6 +97,12 @@ public class MmaDelegate {
}
mmaHelper.event(MmaDelegate.LAUNCH_BROWSER);
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mmaHelper.listenOnceForVariableChanges(remoteVariablesListener);
}
});
}
public static void stop() {
@ -211,6 +222,10 @@ public class MmaDelegate {
}
}
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;
@ -224,4 +239,10 @@ public class MmaDelegate {
}
return deviceId;
}
public interface MmaVariablesChangedListener {
void onRemoteVariablesChanged();
void onRemoteVariablesUnavailable();
}
}

Просмотреть файл

@ -13,6 +13,8 @@ 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;
@ -33,4 +35,8 @@ public interface MmaInterface {
@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);
}

Просмотреть файл

@ -9,7 +9,6 @@ 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;
@ -19,15 +18,17 @@ 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 {
@ -47,6 +48,8 @@ public class MmaLeanplumImp implements MmaInterface {
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 {
@ -145,4 +148,45 @@ public class MmaLeanplumImp implements MmaInterface {
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();
}
}
});
}
}

Просмотреть файл

@ -12,6 +12,8 @@ import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import org.mozilla.gecko.firstrun.PanelConfig;
import java.util.Map;
@ -56,4 +58,13 @@ public class MmaStubImp implements MmaInterface {
}
@Override
public PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, boolean useLocalValues) {
return null;
}
@Override
public void listenOnceForVariableChanges(@NonNull MmaDelegate.MmaVariablesChangedListener listener) {
listener.onRemoteVariablesUnavailable();
}
}

Просмотреть файл

@ -33,6 +33,7 @@ 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()
@ -41,14 +42,16 @@ public class AsyncConfigLoader extends AsyncTask<Void, Void, Void> {
* @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;
}
}

Просмотреть файл

@ -38,6 +38,7 @@ 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;
@ -99,11 +100,18 @@ public class SwitchBoard {
* @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;
}
@ -111,11 +119,23 @@ public class SwitchBoard {
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) {
@ -453,4 +473,10 @@ public class SwitchBoard {
long checksum = crc.getValue();
return (int)(checksum % 100L);
}
public interface ConfigStatusListener {
void onExperimentsConfigLoaded();
void onExperimentsConfigLoadFailed();
}
}

Просмотреть файл

@ -39,6 +39,10 @@ public class SplashScreen extends RelativeLayout {
}
}
public void show(final int duration) {
atLeast(duration);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

Просмотреть файл

@ -13,11 +13,11 @@ import android.support.annotation.Nullable;
*/
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";

Просмотреть файл

@ -17,15 +17,14 @@ 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;
@ -126,7 +125,7 @@ public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<A
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)) {