Bug 826400 - Part 3: Simplify homescreen shortcut creation, and use apple-touch-icon if available r=nalexander,jchen
--- 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;