Bug 1289242 - Implement ASOpenURLDelegate to handle URL loading from the detail activity r?sebastian
MozReview-Commit-ID: 8oMkkBDLLNA
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -10,17 +10,16 @@ import android.app.DownloadManager;
import android.os.Environment;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.widget.VideoView;
import android.graphics.Rect;
import org.json.JSONArray;
-import org.mozilla.gecko.activitystream.ActivityStream;
import org.mozilla.gecko.adjust.AdjustHelperInterface;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
import org.mozilla.gecko.Tabs.TabEvents;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.cleanup.FileCleanupController;
@@ -50,16 +49,17 @@ import org.mozilla.gecko.home.HomeConfig
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.home.HomeConfigPrefsBackend;
import org.mozilla.gecko.home.HomeFragment;
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.home.HomeScreen;
import org.mozilla.gecko.home.SearchEngine;
+import org.mozilla.gecko.home.activitystream.ASOpenURLDelegate;
import org.mozilla.gecko.javaaddons.JavaAddonManager;
import org.mozilla.gecko.media.AudioFocusAgent;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.mozglue.SafeIntent;
import org.mozilla.gecko.notifications.NotificationClient;
import org.mozilla.gecko.notifications.ServiceNotificationClient;
import org.mozilla.gecko.overlays.ui.ShareDialog;
@@ -217,16 +217,18 @@ public class BrowserApp extends GeckoApp
private static final int ACTIVITY_REQUEST_PREFERENCES = 1001;
private static final int ACTIVITY_REQUEST_TAB_QUEUE = 2001;
public static final int ACTIVITY_REQUEST_FIRST_READERVIEW_BOOKMARK = 3001;
public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS = 3002;
public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE = 3003;
public static final int ACTIVITY_REQUEST_TRIPLE_READERVIEW = 4001;
public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK = 4002;
public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE = 4003;
+ public static final int ACTIVITY_REQUEST_AS_DETAIL = 5001;
+ public static final int ACTIVITY_RESULT_AS_DETAIL_OPEN = 5002;
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";
private BrowserSearch mBrowserSearch;
@@ -316,17 +318,18 @@ public class BrowserApp extends GeckoApp
private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
(BrowserAppDelegate) new AddToHomeScreenPromotion(),
(BrowserAppDelegate) new ScreenshotDelegate(),
(BrowserAppDelegate) new BookmarkStateChangeDelegate(),
(BrowserAppDelegate) new ReaderViewBookmarkPromotion(),
(BrowserAppDelegate) new ContentNotificationsDelegate(),
(BrowserAppDelegate) new PostUpdateHandler(),
new TelemetryCorePingDelegate(),
- new OfflineTabStatusDelegate()
+ new OfflineTabStatusDelegate(),
+ new ASOpenURLDelegate()
));
@NonNull
private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
private boolean mHasResumed;
@Override
@@ -2706,17 +2709,17 @@ public class BrowserApp extends GeckoApp
// Show the toolbar before hiding about:home so the
// onMetricsChanged callback still works.
if (mDynamicToolbar.isEnabled()) {
mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
}
if (mHomeScreen == null) {
- if (ActivityStream.isEnabled(this)) {
+ if (org.mozilla.gecko.activitystream.ActivityStream.isEnabled(this)) {
final ViewStub asStub = (ViewStub) findViewById(R.id.activity_stream_stub);
mHomeScreen = (HomeScreen) asStub.inflate();
} else {
final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
mHomeScreen = (HomeScreen) homePagerStub.inflate();
// For now these listeners are HomePager specific. In future we might want
// to have a more abstracted data storage, with one Bundle containing all
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ASOpenURLDelegate.java
@@ -0,0 +1,165 @@
+/* -*- 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.home.activitystream;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.annotation.UiThread;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.delegates.BrowserAppDelegate;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.util.StringUtils;
+
+import java.util.EnumSet;
+
+/**
+ * Delegate handling url-opening for the Activity Stream Detail activity.
+ *
+ * Since the Activity Stream detail screens are running in a separate activity, we cannot
+ * open URLs in BrowserApp directly. Instead we either open them when the activity returns (in the case
+ * of directly clicking on a URL) via onActivityResult, or by storing them in a queue (in the case
+ * of URLs opened in the background).
+ *
+ * Persisting background URLs is important because it's possible for the app to be killed between
+ * "opening" the URL (e.g. "open in new tab" from the context menu, after which we remain in
+ * the detail activity) and resuming BrowserApp (the app could be killed in between use of the context
+ * menu and resuming BrowserApp).
+ */
+public class ASOpenURLDelegate extends BrowserAppDelegate {
+ private static final String LOGTAG = StringUtils.safeSubstring(ASOpenURLDelegate.class.getSimpleName(), 0, 23);
+
+ // Hardcode the name so that we can survive future refactorings. (This is highly unlikely, but
+ // it's still possible that URLs are queued, the app is killed, updated to a new version, and
+ // then reopened. Using a fixed name here avoids us having to cope with a class name/package change
+ // if we were to use the classname instead.)
+ private static final String PREFS_FILE_BACKGROUND = "org.mozilla.gecko.home.activitystream.ASOpenURLDelegate";
+
+ private static final String PREFS_KEY_BACKGROUND_LIST = "key_background_list";
+
+ // This implementation assumes that background flags only contain the PRIVATE flag. We use an assert in onCreate
+ // to protect against changes in this assumption. We could potentially serialise all the flags
+ // for this case, but that is brittle and not ideal (see the same reasoning as for hardcoding
+ // PREFS_FILE_BACKGROUND).
+ private static final String KEY_URL = "url";
+ private static final String KEY_PRIVATE = "private";
+ // We are however able to serialise the flags for immediate opening since we are explicitly
+ // imediately open BrowserApp (and we don't persist the url/flags in that case).
+ private static final String KEY_FLAGS = "flags";
+
+ public static void openURL(Activity activity, String url, EnumSet<HomePager.OnUrlOpenListener.Flags> flags) {
+ Intent data = new Intent();
+ data.putExtra(KEY_URL, url);
+
+ Bundle flagsBundle = new Bundle();
+ flagsBundle.putSerializable(KEY_FLAGS, flags);
+ data.putExtra(KEY_FLAGS, flagsBundle);
+
+ activity.setResult(BrowserApp.ACTIVITY_RESULT_AS_DETAIL_OPEN, data);
+ activity.finish();
+ }
+
+ @Override
+ public void onActivityResult(BrowserApp browserApp, int requestCode, int resultCode,
+ Intent data) {
+ if (requestCode == BrowserApp.ACTIVITY_REQUEST_AS_DETAIL &&
+ resultCode == BrowserApp.ACTIVITY_RESULT_AS_DETAIL_OPEN) {
+ final String url = data.getStringExtra(KEY_URL);
+ final Bundle flagsBundle = data.getBundleExtra(KEY_FLAGS);
+ final EnumSet<HomePager.OnUrlOpenListener.Flags> flags = (EnumSet<HomePager.OnUrlOpenListener.Flags>) flagsBundle.getSerializable(KEY_FLAGS);
+
+ browserApp.onUrlOpen(url, flags);
+ }
+ }
+
+ @Override
+ public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
+ // Early return: do nothing if AS is disabled
+ if (!org.mozilla.gecko.activitystream.ActivityStream.isEnabled(browserApp)) {
+ return;
+ }
+
+ super.onCreate(browserApp, savedInstanceState);
+
+ // Assert that no new flags have been added to OnUrlOpenInBackgroundListener: we currently
+ // assume there is only the PRIVATE flag (to open new background tabs as a private tab).
+ // If/when new flags are added, these need to be explicitly handled in in openInBackground()
+ // and onResume.
+ HomePager.OnUrlOpenInBackgroundListener.Flags[] flags = HomePager.OnUrlOpenInBackgroundListener.Flags.values();
+ if (flags.length != 1 ||
+ flags[0] != HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE) {
+ throw new IllegalStateException("ASOpenURLDelegate must be updated to handle new flags in OnUrlOpenInBackgroundListener");
+ }
+ }
+
+ /**
+ * Queue a URL to be opened when BrowserApp resumes.
+ *
+ * Matches the API of onURLOpenedInBackground. Must be run on the UI thread.
+ */
+ @UiThread
+ public static void openInBackground(final Context context, final String url, EnumSet<HomePager.OnUrlOpenInBackgroundListener.Flags> flags) {
+ final SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE_BACKGROUND, Context.MODE_PRIVATE);
+
+ try {
+ final JSONArray list;
+
+ if (prefs.contains(PREFS_KEY_BACKGROUND_LIST)) {
+ final String listString = prefs.getString(PREFS_KEY_BACKGROUND_LIST, "");
+ list = new JSONArray(listString);
+ } else {
+ list = new JSONArray();
+ }
+
+ JSONObject item = new JSONObject();
+ item.put(KEY_URL, url);
+ item.put(KEY_PRIVATE, flags.contains(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE));
+
+ list.put(item);
+
+ prefs.edit().putString(PREFS_KEY_BACKGROUND_LIST, list.toString()).apply();
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Unable to persist URL to open in background", e);
+ }
+ }
+
+
+ @Override
+ public void onResume(BrowserApp browserApp) {
+ // Early return: do nothing if AS is disabled
+ if (!org.mozilla.gecko.activitystream.ActivityStream.isEnabled(browserApp)) {
+ return;
+ }
+
+ final SharedPreferences prefs = browserApp.getSharedPreferences(PREFS_FILE_BACKGROUND, Context.MODE_PRIVATE);
+
+ try {
+ if (prefs.contains(PREFS_KEY_BACKGROUND_LIST)) {
+ final String listString = prefs.getString(PREFS_KEY_BACKGROUND_LIST, "");
+ JSONArray list = new JSONArray(listString);
+
+ final int n = list.length();
+ for (int i = 0; i < n; i++) {
+ JSONObject item = list.getJSONObject(i);
+ boolean isPrivate = item.getBoolean(KEY_PRIVATE);
+ EnumSet<HomePager.OnUrlOpenInBackgroundListener.Flags> flags = isPrivate ? EnumSet.of(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE) :
+ EnumSet.noneOf(HomePager.OnUrlOpenInBackgroundListener.Flags.class);
+ browserApp.onUrlOpenInBackground(item.getString("url"), flags);
+ }
+
+ prefs.edit().remove(PREFS_KEY_BACKGROUND_LIST).apply();
+ }
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Unable to open persisted background tabs", e);
+ }
+ }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -434,16 +434,17 @@ gbjar.sources += ['java/org/mozilla/geck
'GeckoProfilesProvider.java',
'GeckoUpdateReceiver.java',
'GlobalHistory.java',
'GuestSession.java',
'health/HealthRecorder.java',
'health/SessionInformation.java',
'health/StubbedHealthRecorder.java',
'home/activitystream/ActivityStream.java',
+ 'home/activitystream/ASOpenURLDelegate.java',
'home/BookmarkFolderView.java',
'home/BookmarkScreenshotRow.java',
'home/BookmarksListAdapter.java',
'home/BookmarksListView.java',
'home/BookmarksPanel.java',
'home/BrowserSearch.java',
'home/ClientsAdapter.java',
'home/CombinedHistoryAdapter.java',