diff --git a/mobile/android/base/AwesomeBar.java b/mobile/android/base/AwesomeBar.java index 5ba537c7ba8..e8b175554c8 100644 --- a/mobile/android/base/AwesomeBar.java +++ b/mobile/android/base/AwesomeBar.java @@ -42,23 +42,38 @@ 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; @@ -201,6 +216,10 @@ public class AwesomeBar extends Activity implements GeckoEventListener { } }); + 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)); } @@ -355,6 +374,71 @@ public class AwesomeBar extends Activity implements GeckoEventListener { 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; diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index e5a26e76395..4fa06a5ab51 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -38,6 +38,7 @@ 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; @@ -609,28 +610,97 @@ public class GeckoAppShell // "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) { diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 027ab9a1aad..ade71a55308 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -83,6 +83,7 @@ JAVAFILES = \ Tab.java \ Tabs.java \ TabsTray.java \ + gfx/BitmapUtils.java \ gfx/BufferedCairoImage.java \ gfx/CairoGLInfo.java \ gfx/CairoImage.java \ @@ -272,6 +273,8 @@ RES_DRAWABLE_MDPI_V8 = \ $(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 \ @@ -425,6 +428,10 @@ RES_DRAWABLE_LAND_XHDPI_V14 = \ 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 @@ -595,10 +602,14 @@ $(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 diff --git a/mobile/android/base/gfx/BitmapUtils.java b/mobile/android/base/gfx/BitmapUtils.java new file mode 100644 index 00000000000..cbe05c005fe --- /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 + * + * 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); + } +} + diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index ff077b29295..8c2ba13d2b3 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -81,6 +81,10 @@ + + + + diff --git a/mobile/android/base/resources/drawable-hdpi-v8/home_bg.png b/mobile/android/base/resources/drawable-hdpi-v8/home_bg.png new file mode 100644 index 00000000000..8020939e00c Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi-v8/home_bg.png differ diff --git a/mobile/android/base/resources/drawable-hdpi-v8/home_star.png b/mobile/android/base/resources/drawable-hdpi-v8/home_star.png new file mode 100644 index 00000000000..ef4ecf72801 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi-v8/home_star.png differ diff --git a/mobile/android/base/resources/menu/awesomebar_contextmenu.xml b/mobile/android/base/resources/menu/awesomebar_contextmenu.xml new file mode 100644 index 00000000000..2281368c1fc --- /dev/null +++ b/mobile/android/base/resources/menu/awesomebar_contextmenu.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 762514dd19d..543fc6457fc 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -87,4 +87,8 @@ &site_settings_cancel; &site_settings_clear; &site_settings_no_settings; + + &contextmenu_open_new_tab; + &contextmenu_add_to_launcher; + &contextmenu_share;