diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/promotion')
4 files changed, 771 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java new file mode 100644 index 000000000..c1eeb6bd5 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java @@ -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; + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java b/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java new file mode 100644 index 000000000..0f2df8a2c --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java @@ -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()); + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java new file mode 100644 index 000000000..db5a531c6 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java @@ -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(); + } + +} diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java b/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java new file mode 100644 index 000000000..b6b857fb9 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java @@ -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; + } +} |