Bug 711905 - Support adding links to homescreen from awesomebar [r=mfinkle]
authorWes Johnston <wjohnston@mozilla.com>
Tue, 20 Dec 2011 10:28:12 -0500
changeset 83127 d2bb564589fce7f62a5ece863fbc5b002a0e25c5
parent 83126 a128b1fbe56c11904f36e7bd8479fd8362b7f17a
child 83128 6fcec9ac1164af3f4a50fd7822761d9e802bc6a6
push id21732
push userbmo@edmorley.co.uk
push dateTue, 20 Dec 2011 16:54:34 +0000
treeherdermozilla-central@a8506ab2c654 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs711905
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 711905 - Support adding links to homescreen from awesomebar [r=mfinkle]
mobile/android/base/AwesomeBar.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/Makefile.in
mobile/android/base/gfx/BitmapUtils.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/drawable-hdpi-v8/home_bg.png
mobile/android/base/resources/drawable-hdpi-v8/home_star.png
mobile/android/base/resources/menu/awesomebar_contextmenu.xml
mobile/android/base/strings.xml.in
--- a/mobile/android/base/AwesomeBar.java
+++ b/mobile/android/base/AwesomeBar.java
@@ -37,33 +37,48 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
 import android.app.Activity;
 import android.app.ActionBar;
 import android.content.Intent;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.KeyEvent;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.EditorInfo;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ImageButton;
 import android.widget.RelativeLayout;
+import android.widget.ListView;
+
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.db.BrowserDB;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 
 public class AwesomeBar extends Activity implements GeckoEventListener {
     private static final String LOGTAG = "GeckoAwesomeBar";
 
     static final String URL_KEY = "url";
@@ -196,16 +211,20 @@ public class AwesomeBar extends Activity
             public void onFocusChange(View v, boolean hasFocus) {
                 if (!hasFocus) {
                     InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                     imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                 }
             }
         });
 
+        registerForContextMenu(mAwesomeTabs.findViewById(R.id.all_pages_list));
+        registerForContextMenu(mAwesomeTabs.findViewById(R.id.bookmarks_list));
+        registerForContextMenu(mAwesomeTabs.findViewById(R.id.history_list));
+
         GeckoAppShell.registerGeckoEventListener("SearchEngines:Data", this);
         GeckoAppShell.sendEventToGecko(new GeckoEvent("SearchEngines:Get", null));
     }
 
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("SearchEngines:Data")) {
                 mAwesomeTabs.setSearchEngines(message.getJSONArray("searchEngines"));
@@ -350,16 +369,81 @@ public class AwesomeBar extends Activity
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         mAwesomeTabs.destroy();
         GeckoAppShell.unregisterGeckoEventListener("SearchEngines:Data", this);
     }
 
+    private Cursor mContextMenuCursor = null;
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, view, menuInfo);
+
+        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+        ListView list = (ListView) view;
+        Object selecteditem = list.getItemAtPosition(info.position);
+
+        if (!(selecteditem instanceof Cursor)) {
+            mContextMenuCursor = null;
+            return;
+        }
+
+        mContextMenuCursor = (Cursor) selecteditem;
+
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.awesomebar_contextmenu, menu);
+
+        String title = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.TITLE));
+        menu.setHeaderTitle(title);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (mContextMenuCursor == null)
+            return false;
+
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+
+        switch (item.getItemId()) {
+            case R.id.open_new_tab: {
+                String url = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.URL));
+                GeckoApp.mAppContext.loadUrl(url, AwesomeBar.Type.ADD);
+                break;
+            }
+            case R.id.add_to_launcher: {
+                String url = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.URL));
+                byte[] b = (byte[]) mContextMenuCursor.getBlob(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.FAVICON));
+                String title = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.TITLE));
+    
+                Bitmap bitmap = null;
+                if (b != null)
+                    bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
+    
+                GeckoAppShell.createShortcut(title, url, bitmap, "");
+                break;
+            }
+            case R.id.share: {
+                String url = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.URL));
+                String title = mContextMenuCursor.getString(mContextMenuCursor.getColumnIndexOrThrow(URLColumns.TITLE));
+                GeckoAppShell.openUriExternal(url, "text/plain", "", "",
+                                              Intent.ACTION_SEND, title);
+                break;
+            }
+            default: {
+                mContextMenuCursor = null;
+                return super.onContextItemSelected(item);
+            }
+        }
+        mContextMenuCursor = null;
+        return true;
+    }
+
     public static class AwesomeBarEditText extends EditText {
         OnKeyPreImeListener mOnKeyPreImeListener;
 
         public interface OnKeyPreImeListener {
             public boolean onKeyPreIme(View v, int keyCode, KeyEvent event);
         }
 
         public AwesomeBarEditText(Context context, AttributeSet attrs) {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -33,16 +33,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
 import java.nio.channels.*;
@@ -604,38 +605,107 @@ public class GeckoAppShell
     }
     static void scheduleRestart() {
         Log.i(LOGTAG, "scheduling restart");
         gRestartScheduled = true;
     }
 
     // "Installs" an application by creating a shortcut
     static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
-        Log.w(LOGTAG, "createShortcut for " + aURI + " [" + aTitle + "] > " + aType);
-
-        // the intent to be launched by the shortcut
-        Intent shortcutIntent = new Intent();
-        if (aType.equalsIgnoreCase("webapp")) {
-            shortcutIntent.setAction("org.mozilla.gecko.WEBAPP");
-            shortcutIntent.putExtra("args", "--webapp=" + aURI);
-        } else {
-            shortcutIntent.setAction("org.mozilla.gecko.BOOKMARK");
-            shortcutIntent.putExtra("args", "--url=" + aURI);
-        }
-        shortcutIntent.setClassName(GeckoApp.mAppContext,
-                                    GeckoApp.mAppContext.getPackageName() + ".App");
-
-        Intent intent = new Intent();
-        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
-        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
         byte[] raw = Base64.decode(aIconData.substring(22), Base64.DEFAULT);
         Bitmap bitmap = BitmapFactory.decodeByteArray(raw, 0, raw.length);
-        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
-        intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
-        GeckoApp.mAppContext.sendBroadcast(intent);
+        createShortcut(aTitle, aURI, bitmap, aType);
+    }
+
+    public static void createShortcut(final String aTitle, final String aURI, final Bitmap aIcon, final String aType) {
+        getHandler().post(new Runnable() {
+            public void run() {
+                Log.w(LOGTAG, "createShortcut for " + aURI + " [" + aTitle + "] > " + aType);
+        
+                // the intent to be launched by the shortcut
+                Intent shortcutIntent = new Intent();
+                if (aType.equalsIgnoreCase("webapp")) {
+                    shortcutIntent.setAction("org.mozilla.gecko.WEBAPP");
+                    shortcutIntent.putExtra("args", aURI);
+                } else {
+                    shortcutIntent.setAction("org.mozilla.gecko.BOOKMARK");
+                    shortcutIntent.putExtra("args", aURI);
+                }
+                shortcutIntent.setClassName(GeckoApp.mAppContext,
+                                            GeckoApp.mAppContext.getPackageName() + ".App");
+        
+                Intent intent = new Intent();
+                intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+                if (aTitle != null)
+                    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
+                else
+                    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
+                intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon));
+                intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
+                GeckoApp.mAppContext.sendBroadcast(intent);
+            }
+        });
+    }
+
+    static private Bitmap getLauncherIcon(Bitmap aSource) {
+        // The background images are 72px, but Android will resize as needed.
+        // Bigger is better than too small.
+        final int kIconSize = 72;
+        final int kOverlaySize = 32;
+        final int kOffset = 6;
+        final int kRadius = 5;
+
+        Bitmap bitmap = Bitmap.createBitmap(kIconSize, kIconSize, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // draw a base color
+        Paint paint = new Paint();
+        
+        if (aSource == null) {
+            float[] hsv = new float[3];
+            hsv[0] = 32.0f;
+            hsv[1] = 1.0f;
+            hsv[2] = 1.0f;
+            paint.setColor(Color.HSVToColor(hsv));
+            canvas.drawRoundRect(new RectF(kOffset, kOffset, kIconSize - kOffset, kIconSize - kOffset), kRadius, kRadius, paint);
+        } else {
+            int color = BitmapUtils.getDominantColor(aSource);
+            paint.setColor(color);
+            canvas.drawRoundRect(new RectF(kOffset, kOffset, kIconSize - kOffset, kIconSize - kOffset), kRadius, kRadius, paint);
+            paint.setColor(Color.argb(100, 255, 255, 255));
+            canvas.drawRoundRect(new RectF(kOffset, kOffset, kIconSize - kOffset, kIconSize - kOffset), kRadius, kRadius, paint);
+        }
+
+        // draw the overlay
+        Bitmap overlay = BitmapFactory.decodeResource(GeckoApp.mAppContext.getResources(), R.drawable.home_bg);
+        canvas.drawBitmap(overlay, null, new Rect(0,0,kIconSize, kIconSize), null);
+
+        // draw the bitmap
+        if (aSource == null)
+            aSource = BitmapFactory.decodeResource(GeckoApp.mAppContext.getResources(), R.drawable.home_star);
+
+        if (aSource.getWidth() < kOverlaySize || aSource.getHeight() < kOverlaySize) {
+            canvas.drawBitmap(aSource,
+                              null,
+                              new Rect(kIconSize/2 - kOverlaySize/2,
+                                       kIconSize/2 - kOverlaySize/2,
+                                       kIconSize/2 + kOverlaySize/2,
+                                       kIconSize/2 + kOverlaySize/2),
+                              null);
+        } else {
+            canvas.drawBitmap(aSource,
+                              null,
+                              new Rect(kIconSize/2 - aSource.getWidth()/2,
+                                       kIconSize/2 - aSource.getHeight()/2,
+                                       kIconSize/2 + aSource.getWidth()/2,
+                                       kIconSize/2 + aSource.getHeight()/2),
+                              null);
+        }
+
+        return bitmap;
     }
 
     static String[] getHandlersForMimeType(String aMimeType, String aAction) {
         Intent intent = getIntentForActionString(aAction);
         if (aMimeType != null && aMimeType.length() > 0)
             intent.setType(aMimeType);
         return getHandlersForIntent(intent);
     }
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -78,16 +78,17 @@ JAVAFILES = \
   GlobalHistory.java \
   LinkPreference.java \
   ProfileMigrator.java \
   PromptService.java \
   SurfaceLockInfo.java \
   Tab.java \
   Tabs.java \
   TabsTray.java \
+  gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
   gfx/CairoImage.java \
   gfx/CairoUtils.java \
   gfx/FloatSize.java \
   gfx/GeckoSoftwareLayerClient.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
@@ -267,16 +268,18 @@ RES_DRAWABLE_MDPI_V8 = \
   res/drawable-mdpi-v8/doorhanger_shadow_bg.9.png \
   res/drawable-mdpi-v8/doorhanger_popup_bg.9.png \
   res/drawable-mdpi-v8/site_security_identified.png \
   res/drawable-mdpi-v8/site_security_verified.png \
   res/drawable-mdpi-v8/urlbar_stop.png \
   $(NULL)
 
 RES_DRAWABLE_HDPI_V8 = \
+  res/drawable-hdpi-v8/home_bg.png \
+  res/drawable-hdpi-v8/home_star.png \
   res/drawable-hdpi-v8/abouthome_icon.png \
   res/drawable-hdpi-v8/abouthome_logo.png \
   res/drawable-hdpi-v8/abouthome_separator.9.png \
   res/drawable-hdpi-v8/abouthome_topsite_placeholder.png \
   res/drawable-hdpi-v8/abouthome_topsite_shadow.9.png \
   res/drawable-hdpi-v8/ic_awesomebar_go.png \
   res/drawable-hdpi-v8/ic_awesomebar_search.png \
   res/drawable-hdpi-v8/ic_menu_bookmark_add.png \
@@ -420,16 +423,20 @@ RES_DRAWABLE_LAND_XHDPI_V14 = \
   res/drawable-land-xhdpi-v14/urlbar_stop.png \
   res/drawable-land-xhdpi-v14/site_security_identified.png \
   res/drawable-land-xhdpi-v14/site_security_verified.png \
   $(NULL)
 
 RES_COLOR = \
   res/color/awesomebar_tab_text.xml
 
+RES_MENU = \
+  res/menu/awesomebar_contextmenu.xml \
+  $(NULL)
+
 AB_rCD = $(shell echo $(AB_CD) | sed -e s/-/-r/)
 
 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
 
 ifdef MOZ_CRASHREPORTER
 PROCESSEDJAVAFILES += CrashReporter.java
 MOZ_ANDROID_DRAWABLES += mobile/android/base/resources/drawable/crash_reporter.png
 RES_LAYOUT += res/layout/crash_reporter.xml
@@ -590,17 +597,21 @@ RES_DRAWABLE = $(addprefix res/drawable/
 $(RES_DRAWABLE_LAND_XHDPI_V14): $(subst res/,$(srcdir)/resources/,$(RES_DRAWABLE_LAND_XHDPI_V14))
 	$(NSINSTALL) -D res/drawable-land-xhdpi-v14
 	$(NSINSTALL) $(srcdir)/resources/drawable-land-xhdpi-v14/* res/drawable-land-xhdpi-v14/
 
 $(RES_COLOR): $(subst res/,$(srcdir)/resources/,$(RES_COLOR))
 	$(NSINSTALL) -D res/color
 	$(NSINSTALL) $^  res/color
 
-R.java: $(MOZ_APP_ICON) $(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_DRAWABLE) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_MDPI_V8) $(RES_DRAWABLE_HDPI_V8) $(RES_DRAWABLE_MDPI_V9) $(RES_DRAWABLE_HDPI_V9) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) res/drawable/icon.png res/drawable-hdpi/icon.png res/values/strings.xml AndroidManifest.xml
+$(RES_MENU): $(subst res/,$(srcdir)/resources/,$(RES_MENU))
+	$(NSINSTALL) -D res/menu
+	$(NSINSTALL) $^  res/menu
+
+R.java: $(MOZ_APP_ICON) $(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_DRAWABLE) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_MDPI_V8) $(RES_DRAWABLE_HDPI_V8) $(RES_DRAWABLE_MDPI_V9) $(RES_DRAWABLE_HDPI_V9) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) res/drawable/icon.png res/drawable-hdpi/icon.png res/values/strings.xml AndroidManifest.xml
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -J . --custom-package org.mozilla.gecko
 
-gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png $(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_DRAWABLE) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_MDPI_V8) $(RES_DRAWABLE_HDPI_V8) $(RES_DRAWABLE_MDPI_V9) $(RES_DRAWABLE_HDPI_V9) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) res/values/strings.xml FORCE
+gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png $(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_DRAWABLE) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_MDPI_V8) $(RES_DRAWABLE_HDPI_V8) $(RES_DRAWABLE_MDPI_V9) $(RES_DRAWABLE_HDPI_V9) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) res/values/strings.xml FORCE
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar  -S res -F $@
 
 libs:: classes.dex package-name.txt
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 	$(INSTALL) package-name.txt $(FINAL_TARGET)
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/BitmapUtils.java
@@ -0,0 +1,90 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Wes Johnston <wjohnston@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.Color;
+import android.graphics.Bitmap;
+import java.lang.Math;
+import android.util.Log;
+
+public final class BitmapUtils {
+    public static int getDominantColor(Bitmap source) {
+      int[] colors = new int[37];
+      int[] sat = new int[11];
+      int[] val = new int[11];
+      int maxH = 0;
+      int maxS = 0;
+      int maxV = 0;
+      if (source == null)
+        return Color.argb(255,255,255,255);
+
+      for (int row = 0; row < source.getHeight(); row++) {
+        for (int col = 0; col < source.getWidth(); col++) {
+          int c = source.getPixel(col, row);
+          if (Color.alpha(c) < 128)
+            continue;
+
+          float[] hsv = new float[3];
+          Color.colorToHSV(c, hsv);
+
+          // arbitrarily chosen values for "white" and "black"
+          if (hsv[1] > 0.35f && hsv[2] > 0.35f) {
+            int h = Math.round(hsv[0] / 10.0f);
+            int s = Math.round(hsv[1] * 10.0f);
+            int v = Math.round(hsv[2] * 10.0f);
+            colors[h]++;
+            sat[s]++;
+            val[v]++;
+            // we only care about the most unique non white or black hue, but also
+            // store its saturation and value params to match the color better
+            if (colors[h] > colors[maxH]) {
+              maxH = h;
+              maxS = s;
+              maxV = v;
+            }
+          }
+        }
+      }
+      float[] hsv = new float[3];
+      hsv[0] = maxH*10.0f;
+      hsv[1] = (float)maxS/10.0f;
+      hsv[2] = (float)maxV/10.0f;
+      return Color.HSVToColor(hsv);
+    }
+}
+
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -76,12 +76,16 @@
 
 <!ENTITY addons "Add-ons">
 
 <!ENTITY share "Share">
 <!ENTITY save_as_pdf "Save as PDF">
 <!ENTITY agent_request_desktop "Request Desktop Site">
 <!ENTITY agent_request_mobile "Request Mobile Site">
 
+<!ENTITY contextmenu_open_new_tab "Open in New Tab">
+<!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
+<!ENTITY contextmenu_share "Share">
+
 <!ENTITY site_settings_title        "Clear Site Settings">
 <!ENTITY site_settings_cancel       "Cancel">
 <!ENTITY site_settings_clear        "Clear">
 <!ENTITY site_settings_no_settings  "There are no settings to clear.">
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8020939e00c853025012a2b2ae0353fe74d54b3a
GIT binary patch
literal 2169
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ef4ecf728019a8df5b8edf9049c6c9f35f54d8f0
GIT binary patch
literal 4040
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
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/awesomebar_contextmenu.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/open_new_tab"
+          android:title="@string/contextmenu_open_new_tab"/>
+
+    <item android:id="@+id/share"
+          android:title="@string/contextmenu_share"/>
+
+    <item android:id="@+id/add_to_launcher"
+          android:title="@string/contextmenu_add_to_launcher"/>
+
+</menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -82,9 +82,13 @@
   <string name="forward">&forward;</string>
   <string name="new_tab">&new_tab;</string>
   <string name="addons">&addons;</string>
 
   <string name="site_settings_title">&site_settings_title;</string>
   <string name="site_settings_cancel">&site_settings_cancel;</string>
   <string name="site_settings_clear">&site_settings_clear;</string>
   <string name="site_settings_no_settings">&site_settings_no_settings;</string>
+
+  <string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
+  <string name="contextmenu_add_to_launcher">&contextmenu_add_to_launcher;</string>
+  <string name="contextmenu_share">&contextmenu_share;</string>
 </resources>