Bug 1377286 - Allow pinning items to Activity Stream from other home panels r=sebastian draft
authorGrigory Kruglov <gkruglov@mozilla.com>
Wed, 12 Jul 2017 17:58:45 -0400
changeset 607805 4eefb81224c1cc00ba2eb6147e26119af647d317
parent 607804 61b8b6c7cb908826835286fac061762d60d2198e
child 637156 443087a22b0bfae0fec64ea07114d626cb6aed35
push id68119
push userbmo:gkruglov@mozilla.com
push dateWed, 12 Jul 2017 21:59:25 +0000
reviewerssebastian
bugs1377286
milestone56.0a1
Bug 1377286 - Allow pinning items to Activity Stream from other home panels r=sebastian Menu item to pin/unpin items from Bookmarks and History panels is only displayed when Activity Stream is enabled. MozReview-Commit-ID: Ko3xmpF2R53
mobile/android/app/src/main/res/menu/home_contextmenu.xml
mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/app/src/main/res/menu/home_contextmenu.xml
+++ b/mobile/android/app/src/main/res/menu/home_contextmenu.xml
@@ -27,15 +27,18 @@
           android:title="@string/contextmenu_top_sites_unpin"/>
 
     <item android:id="@+id/home_edit_bookmark"
           android:title="@string/contextmenu_edit_bookmark"/>
 
     <item android:id="@+id/home_remove"
           android:title="@string/contextmenu_remove"/>
 
+    <item android:id="@+id/home_as_pin"
+        android:title="@string/contextmenu_top_sites_pin"/>
+
     <item android:id="@+id/home_add_to_launcher"
           android:title="@string/contextmenu_add_to_launcher"/>
 
     <item android:id="@+id/home_set_as_homepage"
           android:title="@string/contextmenu_set_as_homepage"/>
 
 </menu>
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.database.Cursor;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.AdapterView.AdapterContextMenuInfo;
 import android.widget.ExpandableListAdapter;
 import android.widget.ListAdapter;
 
 /**
  * A ContextMenuInfo for HomeListView
@@ -22,16 +23,18 @@ public class HomeContextMenuInfo extends
 
     public String url;
     public String title;
     /* package-private */ boolean isFolder;
     /* package-private */ int historyId = -1;
     public int bookmarkId = -1;
     public RemoveItemType itemType = null;
 
+    /* package-private */ @Nullable Boolean isAsPinned;
+
     // Item type to be handled with "Remove" selection.
     /* package-private */ enum RemoveItemType {
         BOOKMARKS, COMBINED, HISTORY
     }
 
     public HomeContextMenuInfo(View targetView, int position, long id) {
         super(targetView, position, id);
     }
@@ -50,16 +53,20 @@ public class HomeContextMenuInfo extends
     /* package-private */ boolean hasPartnerBookmarkId() {
         return bookmarkId <= BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START;
     }
 
     /* package-private */ boolean canRemove() {
         return hasBookmarkId() || hasHistoryId() || hasPartnerBookmarkId();
     }
 
+    /* package-private */ void updateAsPinned(boolean pinnedState) {
+        isAsPinned = pinnedState;
+    }
+
     private boolean hasHistoryId() {
         return historyId > -1;
     }
 
     /**
      * Interface for creating ContextMenuInfo instances from cursors.
      */
     public interface Factory {
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -11,16 +11,17 @@ import java.util.EnumSet;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.bookmarks.BookmarkUtils;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
@@ -157,17 +158,17 @@ public abstract class HomeFragment exten
     }
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         if (!(menuInfo instanceof HomeContextMenuInfo)) {
             return;
         }
 
-        HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
+        final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
 
         // Don't show the context menu for folders if full bookmark management isn't enabled.
         final boolean enableFullBookmarkManagement = BookmarkUtils.isEnabled(getContext());
         if (info.isFolder && !enableFullBookmarkManagement) {
             return;
         }
 
         MenuInflater inflater = new MenuInflater(view.getContext());
@@ -204,17 +205,51 @@ public abstract class HomeFragment exten
         // Hide unused menu items for bookmark folder.
         if (info.isFolder) {
             menu.findItem(R.id.home_open_new_tab).setVisible(false);
             menu.findItem(R.id.home_open_private_tab).setVisible(false);
             menu.findItem(R.id.home_copyurl).setVisible(false);
             menu.findItem(R.id.home_share).setVisible(false);
             menu.findItem(R.id.home_add_to_launcher).setVisible(false);
             menu.findItem(R.id.home_set_as_homepage).setVisible(false);
+
+            menu.findItem(R.id.home_as_pin).setVisible(false);
+            return;
         }
+
+        // If Activity Stream is disabled, simply hide "AS Pin" menu item as classic Top Sites do not
+        // support pinning from outside of the Top Site tiles.
+        if (!ActivityStream.isEnabled(getContext())) {
+            menu.findItem(R.id.home_as_pin).setVisible(false);
+            return;
+        }
+
+        // Asynchronously update pin state for this item.
+        // This code should be superseded by context menu unification work in Bug 1377292.
+        final MenuItem asPinItem = menu.findItem(R.id.home_as_pin);
+        // Do not let user interact with this menu item before we figure out pinned state.
+        asPinItem.setEnabled(false);
+
+        (new UIAsyncTask.WithoutParams<Boolean>(ThreadUtils.getBackgroundHandler()) {
+            @Override
+            protected Boolean doInBackground() {
+                return BrowserDB.from(getContext()).isPinnedForAS(
+                        getContext().getContentResolver(), info.url);
+            }
+
+            @Override
+            protected void onPostExecute(Boolean hasPin) {
+                if (hasPin) {
+                    asPinItem.setTitle(R.string.contextmenu_top_sites_unpin);
+                }
+
+                info.updateAsPinned(hasPin);
+                asPinItem.setEnabled(true);
+            }
+        }).execute();
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         // onContextItemSelected() is first dispatched to the activity and
         // then dispatched to its fragments. Since fragments cannot "override"
         // menu item selection handling, it's better to avoid menu id collisions
         // between the activity and its fragments.
@@ -322,16 +357,22 @@ public abstract class HomeFragment exten
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
             final SharedPreferences.Editor editor = prefs.edit();
             editor.putString(GeckoPreferences.PREFS_HOMEPAGE, info.url);
             editor.apply();
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
                 getResources().getResourceEntryName(itemId));
             return true;
         }
+
+        if (itemId == R.id.home_as_pin) {
+            new ToggleASPinTask(getActivity(), info).execute();
+            return true;
+        }
+
         return false;
     }
 
     @Override
     public void setUserVisibleHint (boolean isVisibleToUser) {
         if (isVisibleToUser == getUserVisibleHint()) {
             return;
         }
@@ -394,16 +435,69 @@ public abstract class HomeFragment exten
         if (!canLoad() || mIsLoaded) {
             return;
         }
 
         load();
         mIsLoaded = true;
     }
 
+    private static class ToggleASPinTask extends UIAsyncTask.WithoutParams<Void> {
+        private final WeakReference<Activity> activityWeakReference;
+        private final Context context;
+        private final HomeContextMenuInfo info;
+        private final BrowserDB db;
+        private final ContentResolver cr;
+        private final boolean toggleWillPin;
+
+        ToggleASPinTask(Activity activity, HomeContextMenuInfo info) {
+            super(ThreadUtils.getBackgroundHandler());
+
+            this.activityWeakReference = new WeakReference<Activity>(activity);
+            this.context = activity.getApplicationContext();
+            this.db = BrowserDB.from(context);
+            this.info = info;
+            this.cr = context.getContentResolver();
+
+            if (info.isAsPinned == null) {
+                throw new IllegalStateException("Tried changing pinned state before it's determined.");
+            }
+
+            this.toggleWillPin = !info.isAsPinned;
+        }
+
+        @Override
+        protected Void doInBackground() {
+            if (toggleWillPin) {
+                db.pinSiteForAS(cr, info.url, info.title);
+            } else {
+                db.unpinSiteForAS(cr, info.url);
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void aVoid) {
+            final Activity activity = activityWeakReference.get();
+            if (activity == null || activity.isFinishing()) {
+                return;
+            }
+
+            int messageId = R.string.home_pinned_site;
+            if (!toggleWillPin) {
+                messageId = R.string.home_unpinned_site;
+            }
+
+            SnackbarBuilder.builder(activity)
+                    .message(messageId)
+                    .duration(Snackbar.LENGTH_LONG)
+                    .buildAndShow();
+        }
+    }
+
     static class RemoveItemTask extends UIAsyncTask.WithoutParams<Void> {
         private final WeakReference<Activity> activityWeakReference;
         private final Context context;
         private final HomeContextMenuInfo info;
         private final int position;
         private final BrowserDB db;
 
         /**
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -620,16 +620,21 @@
 <!ENTITY home_remote_tabs_many_hidden_devices "&formatD; devices hidden">
 <!-- Localization note (home_remote_tabs_hidden_devices_title) : This is the
      title of a dialog; we expect more than one device. -->
 <!ENTITY home_remote_tabs_hidden_devices_title "Hidden devices">
 <!-- Localization note (home_remote_tabs_unhide_selected_devices) : This is
      the text of a button; we expect more than one device. -->
 <!ENTITY home_remote_tabs_unhide_selected_devices "Unhide selected devices">
 
+<!-- Localization note (home_pinned_site) : This is a snackbar label displayed after
+    a site is pinned or unpinned. -->
+<!ENTITY home_pinned_site "Pinned site">
+<!ENTITY home_unpinned_site "Unpinned site">
+
 <!ENTITY remote_tabs_panel_moved_title "Where did my tabs go?">
 <!ENTITY remote_tabs_panel_moved_desc "We\'ve moved your tabs from other devices into a panel on your home page that can be easily accessed every time you open a new tab.">
 <!ENTITY remote_tabs_panel_moved_link "Take me to my new panel.">
 
 <!ENTITY pin_site_dialog_hint "Enter a search keyword">
 
 <!ENTITY filepicker_title "Choose File">
 <!ENTITY filepicker_audio_title "Choose or record a sound">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -476,16 +476,19 @@
   <string name="home_most_recent_emptyhint">&home_most_recent_emptyhint2;</string>
   <string name="home_default_empty">&home_default_empty;</string>
   <string name="home_move_back_to_filter">&home_move_back_to_filter;</string>
   <string name="home_remote_tabs_many_hidden_devices">&home_remote_tabs_many_hidden_devices;</string>
   <string name="home_remote_tabs_hidden_devices_title">&home_remote_tabs_hidden_devices_title;</string>
   <string name="home_remote_tabs_unhide_selected_devices">&home_remote_tabs_unhide_selected_devices;</string>
   <string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
 
+  <string name="home_pinned_site">&home_pinned_site;</string>
+  <string name="home_unpinned_site">&home_unpinned_site;</string>
+
   <string name="remote_tabs_never_synced">&remote_tabs_never_synced;</string>
 
   <string name="filepicker_title">&filepicker_title;</string>
   <string name="filepicker_audio_title">&filepicker_audio_title;</string>
   <string name="filepicker_image_title">&filepicker_image_title;</string>
   <string name="filepicker_video_title">&filepicker_video_title;</string>
 
   <!-- Default bookmarks. We used to use bookmark titles shared with XUL from mobile's