mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 00:38:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
|
|
@ -0,0 +1,237 @@
|
|||
/* -*- 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.promotion;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.UrlAnnotations;
|
||||
import org.mozilla.gecko.delegates.TabsTrayVisibilityAwareDelegate;
|
||||
import org.mozilla.gecko.Experiments;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import ch.boye.httpclientandroidlib.util.TextUtils;
|
||||
|
||||
/**
|
||||
* Promote "Add to home screen" if user visits website often.
|
||||
*/
|
||||
public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
|
||||
private static class URLHistory {
|
||||
public final long visits;
|
||||
public final long lastVisit;
|
||||
|
||||
private URLHistory(long visits, long lastVisit) {
|
||||
this.visits = visits;
|
||||
this.lastVisit = lastVisit;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "GeckoPromoteShortcut";
|
||||
|
||||
private static final String EXPERIMENT_MINIMUM_TOTAL_VISITS = "minimumTotalVisits";
|
||||
private static final String EXPERIMENT_LAST_VISIT_MINIMUM_AGE = "lastVisitMinimumAgeMs";
|
||||
private static final String EXPERIMENT_LAST_VISIT_MAXIMUM_AGE = "lastVisitMaximumAgeMs";
|
||||
|
||||
private WeakReference<Activity> activityReference;
|
||||
private boolean isEnabled;
|
||||
private int minimumVisits;
|
||||
private int lastVisitMinimumAgeMs;
|
||||
private int lastVisitMaximumAgeMs;
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
|
||||
super.onCreate(browserApp, savedInstanceState);
|
||||
activityReference = new WeakReference<Activity>(browserApp);
|
||||
|
||||
initializeExperiment(browserApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(BrowserApp browserApp) {
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(BrowserApp browserApp) {
|
||||
Tabs.unregisterOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
private void initializeExperiment(Context context) {
|
||||
if (!SwitchBoard.isInExperiment(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN)) {
|
||||
Log.v(LOGTAG, "Experiment not enabled");
|
||||
// Experiment is not enabled. No need to try to read values.
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject values = SwitchBoard.getExperimentValuesFromJson(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
|
||||
if (values == null) {
|
||||
// We didn't get any values for this experiment. Let's disable it instead of picking default
|
||||
// values that might be bad.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
initializeWithValues(
|
||||
values.getInt(EXPERIMENT_MINIMUM_TOTAL_VISITS),
|
||||
values.getInt(EXPERIMENT_LAST_VISIT_MINIMUM_AGE),
|
||||
values.getInt(EXPERIMENT_LAST_VISIT_MAXIMUM_AGE));
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Could not read experiment values", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeWithValues(int minimumVisits, int lastVisitMinimumAgeMs, int lastVisitMaximumAgeMs) {
|
||||
this.isEnabled = true;
|
||||
|
||||
this.minimumVisits = minimumVisits;
|
||||
this.lastVisitMinimumAgeMs = lastVisitMinimumAgeMs;
|
||||
this.lastVisitMaximumAgeMs = lastVisitMaximumAgeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(final Tab tab, Tabs.TabEvents msg, String data) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Tabs.getInstance().isSelectedTab(tab)) {
|
||||
// We only ever want to show this promotion for the current tab.
|
||||
return;
|
||||
}
|
||||
|
||||
if (Tabs.TabEvents.LOADED != msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab.isPrivate()) {
|
||||
// Never show the prompt for private browsing tabs.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTabsTrayVisible()) {
|
||||
// We only want to show this prompt if this tab is in the foreground and not on top
|
||||
// of the tabs tray.
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
maybeShowPromotionForUrl(tab.getURL(), tab.getTitle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeShowPromotionForUrl(String url, String title) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Context context = activityReference.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldShowPromotion(context, url, title)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HomeScreenPrompt.show(context, url, title);
|
||||
}
|
||||
|
||||
private boolean shouldShowPromotion(Context context, String url, String title) {
|
||||
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(title)) {
|
||||
// We require an URL and a title for the shortcut.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AboutPages.isAboutPage(url)) {
|
||||
// No promotion for our internal sites.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!url.startsWith("https://")) {
|
||||
// Only promote websites that are served over HTTPS.
|
||||
return false;
|
||||
}
|
||||
|
||||
URLHistory history = getHistoryForURL(context, url);
|
||||
if (history == null) {
|
||||
// There's no history for this URL yet or we can't read it right now. Just ignore.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (history.visits < minimumVisits) {
|
||||
// This URL has not been visited often enough.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (history.lastVisit > System.currentTimeMillis() - lastVisitMinimumAgeMs) {
|
||||
// The last visit is too new. Do not show promotion. This is mostly to avoid that the
|
||||
// promotion shows up for a quick refreshs and in the worst case the last visit could
|
||||
// be the current visit (race).
|
||||
return false;
|
||||
}
|
||||
|
||||
if (history.lastVisit < System.currentTimeMillis() - lastVisitMaximumAgeMs) {
|
||||
// The last visit is to old. Do not show promotion.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasAcceptedOrDeclinedHomeScreenShortcut(context, url)) {
|
||||
// The user has already created a shortcut in the past or actively declined to create one.
|
||||
// Let's not ask again for this url - We do not want to be annoying.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean hasAcceptedOrDeclinedHomeScreenShortcut(Context context, String url) {
|
||||
final UrlAnnotations urlAnnotations = BrowserDB.from(context).getUrlAnnotations();
|
||||
return urlAnnotations.hasAcceptedOrDeclinedHomeScreenShortcut(context.getContentResolver(), url);
|
||||
}
|
||||
|
||||
protected URLHistory getHistoryForURL(Context context, String url) {
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
final BrowserDB browserDB = BrowserDB.from(profile);
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = browserDB.getHistoryForURL(context.getContentResolver(), url);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
return new URLHistory(
|
||||
cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(BrowserContract.History.DATE_LAST_VISITED)));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/* -*- 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.promotion;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.UrlAnnotations;
|
||||
import org.mozilla.gecko.icons.IconCallback;
|
||||
import org.mozilla.gecko.icons.IconResponse;
|
||||
import org.mozilla.gecko.icons.Icons;
|
||||
import org.mozilla.gecko.Experiments;
|
||||
import org.mozilla.gecko.util.ActivityUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
/**
|
||||
* Prompt to promote adding the current website to the home screen.
|
||||
*/
|
||||
public class HomeScreenPrompt extends Locales.LocaleAwareActivity implements IconCallback {
|
||||
private static final String EXTRA_TITLE = "title";
|
||||
private static final String EXTRA_URL = "url";
|
||||
|
||||
private static final String TELEMETRY_EXTRA = "home_screen_promotion";
|
||||
|
||||
private View containerView;
|
||||
private ImageView iconView;
|
||||
private String title;
|
||||
private String url;
|
||||
private boolean isAnimating;
|
||||
private boolean hasAccepted;
|
||||
private boolean hasDeclined;
|
||||
|
||||
public static void show(Context context, String url, String title) {
|
||||
Intent intent = new Intent(context, HomeScreenPrompt.class);
|
||||
intent.putExtra(EXTRA_TITLE, title);
|
||||
intent.putExtra(EXTRA_URL, url);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
fetchDataFromIntent();
|
||||
setupViews();
|
||||
loadShortcutIcon();
|
||||
|
||||
slideIn();
|
||||
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
|
||||
|
||||
// Technically this isn't triggered by a "service". But it's also triggered by a background task and without
|
||||
// actual user interaction.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.SERVICE, TELEMETRY_EXTRA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
|
||||
}
|
||||
|
||||
private void fetchDataFromIntent() {
|
||||
final Bundle extras = getIntent().getExtras();
|
||||
|
||||
title = extras.getString(EXTRA_TITLE);
|
||||
url = extras.getString(EXTRA_URL);
|
||||
}
|
||||
|
||||
private void setupViews() {
|
||||
setContentView(R.layout.homescreen_prompt);
|
||||
|
||||
((TextView) findViewById(R.id.title)).setText(title);
|
||||
|
||||
Uri uri = Uri.parse(url);
|
||||
((TextView) findViewById(R.id.host)).setText(uri.getHost());
|
||||
|
||||
containerView = findViewById(R.id.container);
|
||||
iconView = (ImageView) findViewById(R.id.icon);
|
||||
|
||||
findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
hasAccepted = true;
|
||||
|
||||
addToHomeScreen();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onDecline();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addToHomeScreen() {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GeckoAppShell.createShortcut(title, url);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, TELEMETRY_EXTRA);
|
||||
|
||||
ActivityUtils.goToHomeScreen(HomeScreenPrompt.this);
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void loadShortcutIcon() {
|
||||
Icons.with(this)
|
||||
.pageUrl(url)
|
||||
.skipNetwork()
|
||||
.skipMemory()
|
||||
.forLauncherIcon()
|
||||
.build()
|
||||
.execute(this);
|
||||
}
|
||||
|
||||
private void slideIn() {
|
||||
containerView.setTranslationY(500);
|
||||
containerView.setAlpha(0);
|
||||
|
||||
final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
|
||||
translateAnimator.setDuration(400);
|
||||
|
||||
final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
|
||||
alphaAnimator.setStartDelay(200);
|
||||
alphaAnimator.setDuration(600);
|
||||
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
set.playTogether(alphaAnimator, translateAnimator);
|
||||
set.setStartDelay(400);
|
||||
|
||||
set.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember that the user rejected creating a home screen shortcut for this URL.
|
||||
*/
|
||||
private void rememberRejection() {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final UrlAnnotations urlAnnotations = BrowserDB.from(HomeScreenPrompt.this).getUrlAnnotations();
|
||||
urlAnnotations.insertHomeScreenShortcut(getContentResolver(), url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void slideOut() {
|
||||
if (isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimating = true;
|
||||
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finish();
|
||||
}
|
||||
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
|
||||
// Don't perform an activity-dismiss animation.
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
onDecline();
|
||||
}
|
||||
|
||||
private void onDecline() {
|
||||
if (hasDeclined || hasAccepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
rememberRejection();
|
||||
slideOut();
|
||||
|
||||
// Technically not always an action triggered by the "back" button but with the same effect: Finishing this
|
||||
// activity and going back to the previous one.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, TELEMETRY_EXTRA);
|
||||
|
||||
hasDeclined = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicked outside of the prompt.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
onDecline();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIconResponse(IconResponse response) {
|
||||
iconView.setImageBitmap(response.getBitmap());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.promotion;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
|
||||
import org.mozilla.gecko.reader.ReaderModeUtils;
|
||||
import org.mozilla.gecko.Experiments;
|
||||
|
||||
public class ReaderViewBookmarkPromotion extends BrowserAppDelegateWithReference implements Tabs.OnTabsChangedListener {
|
||||
private static final String PREF_FIRST_RV_HINT_SHOWN = "first_reader_view_hint_shown";
|
||||
private static final String FIRST_READERVIEW_OPEN_TELEMETRYEXTRA = "first_readerview_open_prompt";
|
||||
|
||||
private boolean hasEnteredReaderMode = false;
|
||||
|
||||
@Override
|
||||
public void onResume(BrowserApp browserApp) {
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(BrowserApp browserApp) {
|
||||
Tabs.unregisterOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
|
||||
switch (msg) {
|
||||
case LOCATION_CHANGE:
|
||||
// old url: data
|
||||
// new url: tab.getURL()
|
||||
final boolean enteringReaderMode = ReaderModeUtils.isEnteringReaderMode(data, tab.getURL());
|
||||
|
||||
if (!hasEnteredReaderMode && enteringReaderMode) {
|
||||
hasEnteredReaderMode = true;
|
||||
promoteBookmarking();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(BrowserApp browserApp, int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case BrowserApp.ACTIVITY_REQUEST_TRIPLE_READERVIEW:
|
||||
if (resultCode == BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK) {
|
||||
final Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
if (tab != null) {
|
||||
tab.addBookmark();
|
||||
}
|
||||
} else if (resultCode == BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE) {
|
||||
// Nothing to do: we won't show this promotion again either way.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void promoteBookmarking() {
|
||||
final BrowserApp browserApp = getBrowserApp();
|
||||
if (browserApp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(browserApp);
|
||||
final boolean isEnabled = SwitchBoard.isInExperiment(browserApp, Experiments.TRIPLE_READERVIEW_BOOKMARK_PROMPT);
|
||||
|
||||
// We reuse the same preference as for the first offline reader view bookmark
|
||||
// as we only want to show one of the two UIs (they both explain the same
|
||||
// functionality).
|
||||
if (!isEnabled || prefs.getBoolean(PREF_FIRST_RV_HINT_SHOWN, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleHelperUI.show(browserApp,
|
||||
FIRST_READERVIEW_OPEN_TELEMETRYEXTRA,
|
||||
BrowserApp.ACTIVITY_REQUEST_TRIPLE_READERVIEW,
|
||||
R.string.helper_triple_readerview_open_title,
|
||||
R.string.helper_triple_readerview_open_message,
|
||||
R.drawable.helper_readerview_bookmark, // We share the icon with the usual helper UI
|
||||
R.string.helper_triple_readerview_open_button,
|
||||
BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK,
|
||||
BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE);
|
||||
|
||||
prefs
|
||||
.edit()
|
||||
.putBoolean(PREF_FIRST_RV_HINT_SHOWN, true)
|
||||
.apply();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
/* -*- 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.promotion;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
/**
|
||||
* Generic HelperUI (prompt) that can be populated with an image, title, message and action button.
|
||||
* See show() for usage. This is run as an Activity, results must be handled in the parent Activities
|
||||
* onActivityResult().
|
||||
*/
|
||||
public class SimpleHelperUI extends Locales.LocaleAwareActivity {
|
||||
public static final String PREF_FIRST_RVBP_SHOWN = "first_reader_view_bookmark_prompt_shown";
|
||||
public static final String FIRST_RVBP_SHOWN_TELEMETRYEXTRA = "first_readerview_bookmark_prompt";
|
||||
|
||||
private View containerView;
|
||||
|
||||
private boolean isAnimating;
|
||||
|
||||
private String mTelemetryExtra;
|
||||
|
||||
private static final String EXTRA_TELEMETRYEXTRA = "telemetryextra";
|
||||
private static final String EXTRA_TITLE = "title";
|
||||
private static final String EXTRA_MESSAGE = "message";
|
||||
private static final String EXTRA_IMAGE = "image";
|
||||
private static final String EXTRA_BUTTON = "button";
|
||||
private static final String EXTRA_RESULTCODE_POSITIVE = "positive";
|
||||
private static final String EXTRA_RESULTCODE_NEGATIVE = "negative";
|
||||
|
||||
|
||||
/**
|
||||
* Show a generic helper UI/prompt.
|
||||
*
|
||||
* @param owner The owning Activity, the result of this prompt will be delivered to its
|
||||
* onActivityResult().
|
||||
* @param requestCode The request code for the Activity that will be created, this is passed to
|
||||
* onActivityResult() to identify the prompt.
|
||||
*
|
||||
* @param positiveResultCode The result code passed to onActivityResult() when the button has
|
||||
* been pressed.
|
||||
* @param negativeResultCode The result code passed to onActivityResult() when the prompt was
|
||||
* dismissed, either by pressing outside the prompt or by pressing the
|
||||
* device back button.
|
||||
*/
|
||||
public static void show(Activity owner, String telemetryExtra,
|
||||
int requestCode,
|
||||
@StringRes int title, @StringRes int message,
|
||||
@DrawableRes int image, @StringRes int buttonText,
|
||||
int positiveResultCode, int negativeResultCode) {
|
||||
Intent intent = new Intent(owner, SimpleHelperUI.class);
|
||||
|
||||
intent.putExtra(EXTRA_TELEMETRYEXTRA, telemetryExtra);
|
||||
|
||||
intent.putExtra(EXTRA_TITLE, title);
|
||||
intent.putExtra(EXTRA_MESSAGE, message);
|
||||
|
||||
intent.putExtra(EXTRA_IMAGE, image);
|
||||
intent.putExtra(EXTRA_BUTTON, buttonText);
|
||||
|
||||
intent.putExtra(EXTRA_RESULTCODE_POSITIVE, positiveResultCode);
|
||||
intent.putExtra(EXTRA_RESULTCODE_NEGATIVE, negativeResultCode);
|
||||
|
||||
owner.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mTelemetryExtra = getIntent().getStringExtra(EXTRA_TELEMETRYEXTRA);
|
||||
|
||||
setupViews();
|
||||
|
||||
slideIn();
|
||||
}
|
||||
|
||||
private void setupViews() {
|
||||
final Intent i = getIntent();
|
||||
|
||||
setContentView(R.layout.simple_helper_ui);
|
||||
|
||||
containerView = findViewById(R.id.container);
|
||||
|
||||
((ImageView) findViewById(R.id.image)).setImageResource(i.getIntExtra(EXTRA_IMAGE, 0));
|
||||
|
||||
((TextView) findViewById(R.id.title)).setText(i.getIntExtra(EXTRA_TITLE, 0));
|
||||
|
||||
((TextView) findViewById(R.id.message)).setText(i.getIntExtra(EXTRA_MESSAGE, 0));
|
||||
|
||||
final Button button = ((Button) findViewById(R.id.button));
|
||||
button.setText(i.getIntExtra(EXTRA_BUTTON, 0));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
slideOut();
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, mTelemetryExtra);
|
||||
|
||||
setResult(i.getIntExtra(EXTRA_RESULTCODE_POSITIVE, -1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void slideIn() {
|
||||
containerView.setTranslationY(500);
|
||||
containerView.setAlpha(0);
|
||||
|
||||
final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
|
||||
translateAnimator.setDuration(400);
|
||||
|
||||
final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
|
||||
alphaAnimator.setStartDelay(200);
|
||||
alphaAnimator.setDuration(600);
|
||||
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
set.playTogether(alphaAnimator, translateAnimator);
|
||||
set.setStartDelay(400);
|
||||
|
||||
set.start();
|
||||
}
|
||||
|
||||
private void slideOut() {
|
||||
if (isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimating = true;
|
||||
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finish();
|
||||
}
|
||||
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
|
||||
// Don't perform an activity-dismiss animation.
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
slideOut();
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, mTelemetryExtra);
|
||||
|
||||
setResult(getIntent().getIntExtra(EXTRA_RESULTCODE_NEGATIVE, -1));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicked outside of the prompt.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
slideOut();
|
||||
|
||||
// Not really an action triggered by the "back" button but with the same effect: Finishing this
|
||||
// activity and going back to the previous one.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, mTelemetryExtra);
|
||||
|
||||
setResult(getIntent().getIntExtra(EXTRA_RESULTCODE_NEGATIVE, -1));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue