diff --git a/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java b/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java index e891d6b39238..4ce60ada7941 100644 --- a/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java +++ b/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java @@ -140,4 +140,9 @@ public class BaseGeckoInterface implements GeckoAppShell.GeckoInterface { // Bug 908792: Implement this @Override public void invalidateOptionsMenu() {} + + @Override + public void createShortcut(String title, String URI) { + // By default, do nothing. + } } diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java index 046514187887..4aedd2a6d702 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java @@ -5,12 +5,15 @@ package org.mozilla.gecko; +import android.content.ContentResolver; import android.widget.AdapterView; import android.widget.Button; import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.db.BrowserDB; +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.FullScreenState; import org.mozilla.gecko.gfx.Layer; @@ -111,6 +114,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -1818,6 +1822,66 @@ public abstract class GeckoApp AppConstants.USER_AGENT_FENNEC_MOBILE; } + @Override + public void createShortcut(final String title, final String URI) { + ThreadUtils.assertOnBackgroundThread(); + final BrowserDB db = GeckoProfile.get(getApplicationContext()).getDB(); + + final ContentResolver cr = getContext().getContentResolver(); + final Map> metadata = db.getURLMetadata().getForURLs(cr, + Collections.singletonList(URI), + Collections.singletonList(URLMetadataTable.TOUCH_ICON_COLUMN) + ); + + final Map row = metadata.get(URI); + + 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) { + doCreateShortcut(title, url, favicon); + } + }; + + // Retrieve the icon while bypassing the cache. Homescreen icon creation is a one-off event, hence it isn't + // useful to cache these icons. (Android takes care of storing homescreen icons after a shortcut + // has been created.) + // The cache is also (currently) limited to 32dp, hence we explicitly need to avoid accessing those icons. + // If touchIconURL is null, then Favicons falls back to finding the best possible favicon for + // the site URI, hence we can use this call even when there is no touchIcon defined. + Favicons.getPreferredSizeFaviconForPage(getApplicationContext(), URI, touchIconURL, listener); + } + + private void doCreateShortcut(final String aTitle, final String aURI, final Bitmap aIcon) { + // The intent to be launched by the shortcut. + Intent shortcutIntent = new Intent(); + shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT); + shortcutIntent.setData(Uri.parse(aURI)); + shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, + AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, BitmapUtils.getLauncherIcon(getApplicationContext(), aIcon, GeckoAppShell.getPreferredIconSize())); + + if (aTitle != null) { + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle); + } else { + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI); + } + + // Do not allow duplicate items. + intent.putExtra("duplicate", false); + + intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + getApplicationContext().sendBroadcast(intent); + } + private void processAlertCallback(SafeIntent intent) { String alertName = ""; String alertCookie = ""; diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java index 454736cc9169..57fccfd27b44 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java @@ -22,7 +22,6 @@ 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; @@ -32,15 +31,11 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import android.annotation.SuppressLint; -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.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; @@ -78,10 +73,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.ImageFormat; -import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; @@ -213,9 +205,6 @@ public class GeckoAppShell static private int sDensityDpi; static private int sScreenDepth; - /* Default colors. */ - private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f }; - /* Is the value in sVibrationEndTime valid? */ private static boolean sVibrationMaybePlaying; @@ -833,62 +822,11 @@ public class GeckoAppShell // This is the entry point from nsIShellService. @WrapForJNI public static void createShortcut(final String aTitle, final String aURI) { - ThreadUtils.assertOnBackgroundThread(); - final BrowserDB db = GeckoProfile.get(getApplicationContext()).getDB(); - - final ContentResolver cr = getContext().getContentResolver(); - final Map> metadata = db.getURLMetadata().getForURLs(cr, - Collections.singletonList(aURI), - Collections.singletonList(URLMetadataTable.TOUCH_ICON_COLUMN) - ); - - final Map row = metadata.get(aURI); - - String touchIconURL = null; - - if (row != null) { - touchIconURL = (String) row.get(URLMetadataTable.TOUCH_ICON_COLUMN); + final GeckoInterface geckoInterface = getGeckoInterface(); + if (geckoInterface == null) { + return; } - - OnFaviconLoadedListener listener = new OnFaviconLoadedListener() { - @Override - public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { - doCreateShortcut(aTitle, url, favicon); - } - }; - - // Retrieve the icon while bypassing the cache. Homescreen icon creation is a one-off event, hence it isn't - // useful to cache these icons. (Android takes care of storing homescreen icons after a shortcut - // has been created.) - // The cache is also (currently) limited to 32dp, hence we explicitly need to avoid accessing those icons. - // If touchIconURL is null, then Favicons falls back to finding the best possible favicon for - // the site URI, hence we can use this call even when there is no touchIcon defined. - Favicons.getPreferredSizeFaviconForPage(getApplicationContext(), aURI, touchIconURL, listener); - } - - private static void doCreateShortcut(final String aTitle, final String aURI, final Bitmap aIcon) { - // The intent to be launched by the shortcut. - Intent shortcutIntent = new Intent(); - shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT); - shortcutIntent.setData(Uri.parse(aURI)); - shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, - AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon)); - - if (aTitle != null) { - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle); - } else { - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI); - } - - // Do not allow duplicate items. - intent.putExtra("duplicate", false); - - intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - getApplicationContext().sendBroadcast(intent); + geckoInterface.createShortcut(aTitle, aURI); } @JNITarget @@ -910,60 +848,6 @@ public class GeckoAppShell } } - static private Bitmap getLauncherIcon(Bitmap aSource) { - final int kOffset = 6; - final int kRadius = 5; - int size = getPreferredIconSize(); - int insetSize = aSource != null ? size * 2 / 3 : size; - - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - - - // draw a base color - Paint paint = new Paint(); - if (aSource == null) { - // If we aren't drawing a favicon, just use an orange color. - paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV)); - canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); - } else if (aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) { - // Otherwise, if the icon is large enough, just draw it. - Rect iconBounds = new Rect(0, 0, size, size); - canvas.drawBitmap(aSource, null, iconBounds, null); - return bitmap; - } else { - // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat - int color = BitmapUtils.getDominantColor(aSource); - paint.setColor(color); - canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); - paint.setColor(Color.argb(100, 255, 255, 255)); - canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); - } - - // draw the overlay - Bitmap overlay = BitmapUtils.decodeResource(getApplicationContext(), R.drawable.home_bg); - canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null); - - // draw the favicon - if (aSource == null) - aSource = BitmapUtils.decodeResource(getApplicationContext(), R.drawable.home_star); - - // by default, we scale the icon to this size - int sWidth = insetSize / 2; - int sHeight = sWidth; - - int halfSize = size / 2; - canvas.drawBitmap(aSource, - null, - new Rect(halfSize - sWidth, - halfSize - sHeight, - halfSize + sWidth, - halfSize + sHeight), - null); - - return bitmap; - } - @WrapForJNI(stubName = "GetHandlersForMimeTypeWrapper") static String[] getHandlersForMimeType(String aMimeType, String aAction) { Intent intent = getIntentForActionString(aAction); @@ -2209,6 +2093,13 @@ public class GeckoAppShell public AbsoluteLayout getPluginContainer(); public void notifyCheckUpdateResult(String result); public void invalidateOptionsMenu(); + + /** + * Create a shortcut -- generally a home-screen icon -- linking the given title to the given URI. + * @param title of URI to link to. + * @param URI to link to. + */ + public void createShortcut(String title, String URI); }; private static GeckoInterface sGeckoInterface; diff --git a/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java b/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java index 4d1d5bff40cf..ad48f8cb87e2 100644 --- a/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java +++ b/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java @@ -26,6 +26,9 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -34,6 +37,9 @@ import android.util.Base64; import android.util.Log; public final class BitmapUtils { + /* Default colors. */ + private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f }; + private static final String LOGTAG = "GeckoBitmapUtils"; private BitmapUtils() {} @@ -429,5 +435,57 @@ public final class BitmapUtils { } return icon; } -} + public static Bitmap getLauncherIcon(Context context, Bitmap aSource, int size) { + final int kOffset = 6; + final int kRadius = 5; + int insetSize = aSource != null ? size * 2 / 3 : size; + + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + + // draw a base color + Paint paint = new Paint(); + if (aSource == null) { + // If we aren't drawing a favicon, just use an orange color. + paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV)); + canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); + } else if (aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) { + // Otherwise, if the icon is large enough, just draw it. + Rect iconBounds = new Rect(0, 0, size, size); + canvas.drawBitmap(aSource, null, iconBounds, null); + return bitmap; + } else { + // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat + int color = BitmapUtils.getDominantColor(aSource); + paint.setColor(color); + canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); + paint.setColor(Color.argb(100, 255, 255, 255)); + canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint); + } + + // draw the overlay + Bitmap overlay = BitmapUtils.decodeResource(context, R.drawable.home_bg); + canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null); + + // draw the favicon + if (aSource == null) + aSource = BitmapUtils.decodeResource(context, R.drawable.home_star); + + // by default, we scale the icon to this size + int sWidth = insetSize / 2; + int sHeight = sWidth; + + int halfSize = size / 2; + canvas.drawBitmap(aSource, + null, + new Rect(halfSize - sWidth, + halfSize - sHeight, + halfSize + sWidth, + halfSize + sHeight), + null); + + return bitmap; + } +}