Bug 1258450 - Add GeckoInterface.createShortcut. r=snorp
This moves some Fennec-specific home-screen icon manipulations out of
GeckoAppShell. A GeckoView interface can follow.
MozReview-Commit-ID: 7OhRAT9Agdh
--- a/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -135,9 +135,14 @@ public class BaseGeckoInterface implemen
@Override
public void notifyCheckUpdateResult(String result) {
GeckoAppShell.notifyObservers("Update:CheckResult", result);
}
// Bug 908792: Implement this
@Override
public void invalidateOptionsMenu() {}
+
+ @Override
+ public void createShortcut(String title, String URI) {
+ // By default, do nothing.
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1,21 +1,24 @@
/* -*- 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.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;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PluginLayer;
import org.mozilla.gecko.health.HealthRecorder;
import org.mozilla.gecko.health.SessionInformation;
import org.mozilla.gecko.health.StubbedHealthRecorder;
@@ -106,16 +109,17 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
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;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -1813,16 +1817,76 @@ public abstract class GeckoApp
}
@Override
public String getDefaultUAString() {
return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
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<String, Map<String, Object>> metadata = db.getURLMetadata().getForURLs(cr,
+ Collections.singletonList(URI),
+ Collections.singletonList(URLMetadataTable.TOUCH_ICON_COLUMN)
+ );
+
+ final Map<String, Object> 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 = "";
Uri data = intent.getData();
if (data != null) {
alertName = data.getQueryParameter("name");
if (alertName == null)
alertName = "";
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
@@ -17,35 +17,30 @@ import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.Proxy;
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.StringTokenizer;
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;
import org.mozilla.gecko.mozglue.ContextUtils;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoRequest;
@@ -73,20 +68,17 @@ import android.content.pm.ApplicationInf
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
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;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
@@ -208,19 +200,16 @@ public class GeckoAppShell
/* package */ static NotificationClient notificationClient;
// See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
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;
/* Time (in System.nanoTime() units) when the currently-playing vibration
* is scheduled to end. This value is valid only when
* sVibrationMaybePlaying is true. */
private static long sVibrationEndTime;
@@ -828,72 +817,21 @@ public class GeckoAppShell
static void scheduleRestart() {
getGeckoInterface().doRestart();
}
// Creates a homescreen shortcut for a web page.
// 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<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);
+ 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
static public int getPreferredIconSize() {
if (Versions.feature11Plus) {
ActivityManager am = (ActivityManager)
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
return am.getLauncherLargeIconSize();
@@ -905,70 +843,16 @@ public class GeckoAppShell
return 96;
case DisplayMetrics.DENSITY_HIGH:
default:
return 72;
}
}
}
- 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);
if (aMimeType != null && aMimeType.length() > 0)
intent.setType(aMimeType);
return getHandlersForIntent(intent);
}
@@ -2204,16 +2088,23 @@ public class GeckoAppShell
public void removeAppStateListener(AppStateListener listener);
public View getCameraView();
public void notifyWakeLockChanged(String topic, String state);
public FormAssistPopup getFormAssistPopup();
public boolean areTabsShown();
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;
public static GeckoInterface getGeckoInterface() {
return sGeckoInterface;
}
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/BitmapUtils.java
@@ -21,24 +21,30 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.ThumbnailHelper;
import android.content.Context;
import android.content.res.Resources;
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;
import android.text.TextUtils;
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() {}
public interface BitmapLoader {
public void onBitmapFound(Drawable d);
}
@@ -424,10 +430,62 @@ public final class BitmapUtils {
} catch (Exception e4) {
Log.i(LOGTAG, "Exception getting drawable", e4);
}
resourceUrl = null;
}
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;
+ }
}
-