Bug 826400 - Part 3: Simplify homescreen shortcut creation, and use apple-touch-icon if available r=nalexander,jchen draft
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 29 Dec 2015 11:49:54 -0800
changeset 319140 dbdfaf915ffd0dc1ae3d8c266c484c4a4c635826
parent 319139 074a36a6acc9e3aa8f63c71403a7cc4afdb31032
child 512553 ee0c4ac5e7fc6eabecdc2dedabb8c6b76185906c
push id8984
push userahunt@mozilla.com
push dateTue, 05 Jan 2016 23:06:12 +0000
reviewersnalexander, jchen
bugs826400
milestone46.0a1
Bug 826400 - Part 3: Simplify homescreen shortcut creation, and use apple-touch-icon if available r=nalexander,jchen
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
mobile/android/components/build/nsIShellService.idl
mobile/android/components/build/nsShellService.cpp
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1,15 +1,16 @@
 /* -*- 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;
 
+import android.os.AsyncTask;
 import org.mozilla.gecko.adjust.AdjustHelperInterface;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
@@ -1102,21 +1103,21 @@ public class BrowserApp extends GeckoApp
                         TelemetryContract.Method.DIALOG, extrasId);
 
                     new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
                 } else if (itemId == 1) {
                     final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
                         TelemetryContract.Method.DIALOG, extrasId);
 
-                    String url = tab.getURL();
-                    String title = tab.getDisplayTitle();
-                    Bitmap favicon = tab.getFavicon();
+                    final String url = tab.getURL();
+                    final String title = tab.getDisplayTitle();
+
                     if (url != null && title != null) {
-                        GeckoAppShell.createShortcut(title, url, favicon);
+                        GeckoAppShell.createShortcut(title, url);
                     }
                 }
             }
         });
 
         final PromptListItem[] items = new PromptListItem[2];
         items[0] = new PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
         items[1] = new PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
@@ -1231,34 +1232,34 @@ public class BrowserApp extends GeckoApp
                     Clipboard.setText(url);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "copyurl");
                 }
             }
             return true;
         }
 
         if (itemId == R.id.add_to_launcher) {
-            Tab tab = Tabs.getInstance().getSelectedTab();
+            final Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab == null) {
                 return true;
             }
 
             final String url = tab.getURL();
             final String title = tab.getDisplayTitle();
             if (url == null || title == null) {
                 return true;
             }
 
-            final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
-            Favicons.getSizedFavicon(getContext(),
-                                     url,
-                                     tab.getFaviconURL(),
-                                     Integer.MAX_VALUE,
-                                     LoadFaviconTask.FLAG_PERSIST,
-                                     listener);
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... voids) {
+                    GeckoAppShell.createShortcut(title, url);
+                    return null;
+                }
+            }.execute();
 
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
                 getResources().getResourceEntryName(itemId));
             return true;
         }
 
         return false;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
@@ -18,30 +18,34 @@ import java.io.PipedOutputStream;
 import java.net.MalformedURLException;
 import java.net.Proxy;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 
+import android.content.ContentResolver;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.LocalURLMetadata;
+import org.mozilla.gecko.db.URLMetadataTable;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.prompts.PromptService;
@@ -289,31 +293,16 @@ public class GeckoAppShell
 
     public static native SurfaceBits getSurfaceBits(Surface surface);
 
     public static native void addPresentationSurface(Surface surface);
     public static native void removePresentationSurface(Surface surface);
 
     public static native void onFullScreenPluginHidden(View view);
 
-    public static class CreateShortcutFaviconLoadedListener implements OnFaviconLoadedListener {
-        private final String title;
-        private final String url;
-
-        public CreateShortcutFaviconLoadedListener(final String url, final String title) {
-            this.url = url;
-            this.title = title;
-        }
-
-        @Override
-        public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
-            GeckoAppShell.createShortcut(title, url, favicon);
-        }
-    }
-
     private static LayerView sLayerView;
 
     public static void setLayerView(LayerView lv) {
         if (sLayerView == lv) {
             return;
         }
         sLayerView = lv;
     }
@@ -762,31 +751,51 @@ public class GeckoAppShell
     @WrapForJNI
     static void scheduleRestart() {
         getGeckoInterface().doRestart();
     }
 
     // Creates a homescreen shortcut for a web page.
     // This is the entry point from nsIShellService.
     @WrapForJNI
-    static void createShortcut(final String aTitle, final String aURI, final String aIconData) {
-        // We have the favicon data (base64) decoded on the background thread, callback here, then
-        // call the other createShortcut method with the decoded favicon.
-        // This is slightly contrived, but makes the images available to the favicon cache.
-        Favicons.getSizedFavicon(getApplicationContext(), aURI, aIconData, Integer.MAX_VALUE, 0,
-            new OnFaviconLoadedListener() {
-                @Override
-                public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
-                    createShortcut(aTitle, url, favicon);
-                }
+    public static void createShortcut(final String aTitle, final String aURI) {
+        final BrowserDB db = GeckoProfile.get(getApplicationContext()).getDB();
+
+        final ContentResolver cr = getContext().getContentResolver();
+        final Map<String, Map<String, Object>> metadata = db.getURLMetadata().getForURLs(cr,
+                Collections.singletonList(aURI),
+                Collections.singletonList(URLMetadataTable.TOUCH_ICON_COLUMN)
+        );
+
+        final Map<String, Object> row = metadata.get(aURI);
+
+        String touchIconURL = null;
+
+        if (row != null) {
+            touchIconURL = (String) row.get(URLMetadataTable.TOUCH_ICON_COLUMN);
+        }
+
+        OnFaviconLoadedListener listener = new OnFaviconLoadedListener() {
+            @Override
+            public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
+                createShortcutWithBitmap(aTitle, url, favicon);
             }
-        );
+        };
+
+        if (touchIconURL != null) {
+            // We have the favicon data (base64) decoded on the background thread, callback here, then
+            // call the other createShortcut method with the decoded favicon.
+            // This is slightly contrived, but makes the images available to the favicon cache.
+            Favicons.getSizedFavicon(getApplicationContext(), aURI, touchIconURL, Integer.MAX_VALUE, 0, listener);
+        } else {
+            Favicons.getPreferredSizeFaviconForPage(getApplicationContext(), aURI, listener);
+        }
     }
 
-    public static void createShortcut(final String aTitle, final String aURI, final Bitmap aBitmap) {
+    private static void createShortcutWithBitmap(final String aTitle, final String aURI, final Bitmap aBitmap) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 doCreateShortcut(aTitle, aURI, aBitmap);
             }
         });
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -2,16 +2,17 @@
  * 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;
 
 import java.util.EnumSet;
 
+import android.os.AsyncTask;
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.Restrictions;
 import org.mozilla.gecko.Telemetry;
@@ -213,17 +214,24 @@ public abstract class HomeFragment exten
 
         if (itemId == R.id.home_add_to_launcher) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't add to home screen because URL is null");
                 return false;
             }
 
             // Fetch an icon big enough for use as a home screen icon.
-            Favicons.getPreferredSizeFaviconForPage(context, info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
+            final String displayTitle = info.getDisplayTitle();
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... voids) {
+                    GeckoAppShell.createShortcut(displayTitle, info.url);
+                    return null;
+                }
+            }.execute();
             return true;
         }
 
         if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't open in new tab because URL is null");
                 return false;
             }
--- a/mobile/android/components/build/nsIShellService.idl
+++ b/mobile/android/components/build/nsIShellService.idl
@@ -14,13 +14,13 @@ interface nsIShellService : nsISupports
   void switchTask();
 
   /**
    * This method creates a shortcut on a desktop or homescreen that opens in
    * the our application.
    *
    * @param aTitle     the user-friendly name of the shortcut.
    * @param aURI       the URI to open.
-   * @param aIconData  a base64-encoded data: URI representation of the shortcut's icon, as accepted by the favicon decoder.
+   * @param aIconData  obsolete and ignored, but remains for backward compatibility; pass an empty string
    * @param aIntent    obsolete and ignored, but remains for backward compatibility; pass an empty string
    */
   void createShortcut(in AString aTitle, in AString aURI, in AString aIconData, in AString aIntent);
 };
--- a/mobile/android/components/build/nsShellService.cpp
+++ b/mobile/android/components/build/nsShellService.cpp
@@ -15,16 +15,16 @@ NS_IMPL_ISUPPORTS(nsShellService, nsIShe
 NS_IMETHODIMP
 nsShellService::SwitchTask()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsShellService::CreateShortcut(const nsAString& aTitle, const nsAString& aURI,
-                                const nsAString& aIconData, const nsAString& aIntent)
+                                const nsAString& aIcondata, const nsAString& aIntent)
 {
-  if (!aTitle.Length() || !aURI.Length() || !aIconData.Length())
+  if (!aTitle.Length() || !aURI.Length())
     return NS_ERROR_FAILURE;
 
-  widget::GeckoAppShell::CreateShortcut(aTitle, aURI, aIconData);
+  widget::GeckoAppShell::CreateShortcut(aTitle, aURI);
   return NS_OK;
 }
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -116,19 +116,19 @@ constexpr char GeckoAppShell::CreateMess
 auto GeckoAppShell::CreateMessageCursorWrapper(int64_t a0, int64_t a1, mozilla::jni::ObjectArray::Param a2, int32_t a3, mozilla::jni::String::Param a4, bool a5, bool a6, bool a7, int64_t a8, bool a9, int32_t a10) -> void
 {
     return mozilla::jni::Method<CreateMessageCursorWrapper_t>::Call(nullptr, nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
 }
 
 constexpr char GeckoAppShell::CreateShortcut_t::name[];
 constexpr char GeckoAppShell::CreateShortcut_t::signature[];
 
-auto GeckoAppShell::CreateShortcut(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2) -> void
+auto GeckoAppShell::CreateShortcut(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
 {
-    return mozilla::jni::Method<CreateShortcut_t>::Call(nullptr, nullptr, a0, a1, a2);
+    return mozilla::jni::Method<CreateShortcut_t>::Call(nullptr, nullptr, a0, a1);
 }
 
 constexpr char GeckoAppShell::CreateThreadCursorWrapper_t::name[];
 constexpr char GeckoAppShell::CreateThreadCursorWrapper_t::signature[];
 
 auto GeckoAppShell::CreateThreadCursorWrapper(int32_t a0) -> void
 {
     return mozilla::jni::Method<CreateThreadCursorWrapper_t>::Call(nullptr, nullptr, a0);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -355,28 +355,27 @@ public:
 
 public:
     struct CreateShortcut_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
-                mozilla::jni::String::Param,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "createShortcut";
         static constexpr char signature[] =
-                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
+                "(Ljava/lang/String;Ljava/lang/String;)V";
         static const bool isStatic = true;
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
-    static auto CreateShortcut(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+    static auto CreateShortcut(mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
 
 public:
     struct CreateThreadCursorWrapper_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t> Args;