Bug 667530 - Add ability to add application/bookmark shortcuts to Launcher screens [r=blassey, r=fabrice]
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -22,16 +22,25 @@
<application android:label="@MOZ_APP_DISPLAYNAME@"
android:icon="@drawable/icon"
#if MOZILLA_OFFICIAL
android:debuggable="false">
#else
android:debuggable="true">
#endif
+ <activity android:name="LauncherShortcuts"
+ android:label="@string/launcher_shortcuts_title">
+ <!-- This intent-filter allows your shortcuts to be created in the launcher. -->
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name="App"
android:label="@MOZ_APP_DISPLAYNAME@"
android:configChanges="keyboard|keyboardHidden|mcc|mnc"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:launchMode="singleTask"
android:theme="@style/GreyTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -298,17 +298,17 @@ abstract public class GeckoApp
String uri = intent.getDataString();
GeckoAppShell.sendEventToGecko(new GeckoEvent(uri));
Log.i("GeckoApp","onNewIntent: "+uri);
}
else if (Intent.ACTION_MAIN.equals(action)) {
Log.i("GeckoApp", "Intent : ACTION_MAIN");
GeckoAppShell.sendEventToGecko(new GeckoEvent(""));
}
- else if (action.equals("org.mozilla.fennec.WEBAPP")) {
+ else if (action.equals("org.mozilla.gecko.WEBAPP")) {
String uri = intent.getStringExtra("args");
GeckoAppShell.sendEventToGecko(new GeckoEvent(uri));
Log.i("GeckoApp","Intent : WEBAPP - " + uri);
}
}
@Override
public void onPause()
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -673,17 +673,17 @@ public class GeckoAppShell
gRestartScheduled = true;
}
// "Installs" an application by creating a shortcut
static void installWebApplication(String aURI, String aTitle, String aIconData) {
Log.w("GeckoAppJava", "installWebApplication for " + aURI + " [" + aTitle + "]");
// the intent to be launched by the shortcut
- Intent shortcutIntent = new Intent("org.mozilla.fennec.WEBAPP");
+ Intent shortcutIntent = new Intent("org.mozilla.gecko.WEBAPP");
shortcutIntent.setClassName(GeckoApp.mAppContext,
GeckoApp.mAppContext.getPackageName() + ".App");
shortcutIntent.putExtra("args", "--webapp=" + aURI);
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);
new file mode 100644
--- /dev/null
+++ b/embedding/android/LauncherShortcuts.java.in
@@ -0,0 +1,184 @@
+/* -*- 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):
+ * Vladimir Vukicevic <vladimir@pobox.com>
+ * Wes Johnston <wjohnston@mozilla.com>
+ * Mark Finkle <mfinkle@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+import java.io.*;
+
+import org.mozilla.gecko.*;
+
+import android.os.*;
+import android.content.*;
+import android.app.*;
+import android.text.*;
+import android.util.*;
+import android.widget.*;
+import android.database.sqlite.*;
+import android.database.*;
+import android.view.*;
+import android.net.Uri;
+import android.graphics.*;
+
+
+public class LauncherShortcuts extends ListActivity {
+ public static final String CREATE_SHORTCUT = "org.mozilla.gecko.CREATE_SHORTCUT";
+
+ public class LauncherCursorAdapter extends SimpleCursorAdapter {
+ private Cursor _cursor;
+ private Context _context;
+
+ public LauncherCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
+ // Using the older, deprecated constructor so we can work on API < 11
+ super(context, layout, c, from, to);
+ _cursor = c;
+ _context = context;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ImageView imageView = (ImageView) view.findViewById(R.id.favicon);
+
+ String favicon = cursor.getString(3);
+ byte[] raw = Base64.decode(favicon.substring(22), Base64.DEFAULT);
+ Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeByteArray(raw, 0, raw.length), 48, 48, true);
+ imageView.setImageBitmap(bitmap);
+
+ super.bindView(view, context, cursor);
+ }
+ }
+
+ private Cursor getCursor(Context context) {
+ File home = new File(context.getFilesDir(), "mozilla");
+ if (!home.exists())
+ return null;
+
+ File profile = null;
+ String[] files = home.list();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].endsWith(".default")) {
+ profile = new File(home, files[i]);
+ break;
+ }
+ }
+
+ if (profile == null)
+ return null;
+
+ File webapps = new File(profile, "webapps.sqlite");
+ if (!webapps.exists())
+ return null;
+
+ Log.i("LauncherShortcuts", "Opening: " + webapps.getPath());
+ mDb = SQLiteDatabase.openDatabase(webapps.getPath(), null, SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
+ return mDb.rawQuery("SELECT rowid as _id, title, uri, icon FROM webapps", null);
+ }
+
+ private Cursor mCursor;
+ private SQLiteDatabase mDb;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.launch_app_list);
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+ mCursor = getCursor(this);
+ if (mCursor != null) {
+ // After selecting an item, the empty view can flash on screen. Clear
+ // the text so we don't see it.
+ TextView emptyText = (TextView)findViewById(android.R.id.empty);
+ emptyText.setText("");
+
+ // Load the list using a custom adapter so we can create the bitmaps
+ ListAdapter adapter = new LauncherCursorAdapter(
+ this,
+ R.layout.launch_app_listitem,
+ mCursor,
+ new String[] {"title"},
+ new int[] {R.id.title}
+ );
+ setListAdapter(adapter);
+ }
+ }
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ mCursor.moveToPosition(position);
+
+ Intent shortcutintent = new Intent("org.mozilla.gecko.WEBAPP");
+ shortcutintent.setClass(this, App.class);
+ shortcutintent.putExtra("args", "--webapp=" + mCursor.getString(2));
+
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, mCursor.getString(1));
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutintent);
+
+ String favicon = mCursor.getString(3);
+ byte[] raw = Base64.decode(favicon.substring(22), Base64.DEFAULT);
+
+ DisplayMetrics dm = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(dm);
+ int size;
+ switch (dm.densityDpi) {
+ case DisplayMetrics.DENSITY_MEDIUM:
+ size = 48;
+ break;
+ case DisplayMetrics.DENSITY_HIGH:
+ size = 72;
+ break;
+ default:
+ size = 72;
+ }
+
+ Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeByteArray(raw, 0, raw.length), size, size, true);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
+
+ // Now, return the result to the launcher
+ setResult(RESULT_OK, intent);
+ mDb.close();
+ mCursor.close();
+ finish();
+ }
+}
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -54,16 +54,17 @@ JAVAFILES = \
GeckoInputConnection.java \
AlertNotification.java \
$(NULL)
PROCESSEDJAVAFILES = \
App.java \
Restarter.java \
NotificationHandler.java \
+ LauncherShortcuts.java \
$(NULL)
ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
MIN_CPU_VERSION=7
else
MIN_CPU_VERSION=5
endif
@@ -111,16 +112,18 @@ ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_
ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
DEFINES += -DMOZ_ANDROID_SHARED_ID="$(ANDROID_PACKAGE_NAME).sharedID"
endif
RES_LAYOUT = \
res/layout/notification_progress.xml \
res/layout/notification_progress_text.xml \
res/layout/notification_icon_text.xml \
+ res/layout/launch_app_list.xml \
+ res/layout/launch_app_listitem.xml \
$(NULL)
RES_VALUES = res/values/colors.xml res/values/themes.xml
AB_rCD = $(shell echo $(AB_CD) | sed -e s/-/-r/)
JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
--- a/embedding/android/locales/en-US/android_strings.dtd
+++ b/embedding/android/locales/en-US/android_strings.dtd
@@ -11,8 +11,11 @@
<!ENTITY crash_help_message "Please help us fix this problem!">
<!ENTITY crash_send_report_message "Send Mozilla a crash report">
<!ENTITY crash_include_url "Include page address">
<!ENTITY crash_close_label "Close">
<!ENTITY crash_restart_label "Restart &brandShortName;">
<!ENTITY sending_crash_report "Sending crash report\u2026">
<!ENTITY exit_label "Exit">
<!ENTITY continue_label "Continue">
+
+<!ENTITY launcher_shortcuts_title "&brandShortName; Web Apps">
+<!ENTITY launcher_shortcuts_empty "No web apps were found">
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/launch_app_list.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="3dip"
+ android:orientation="vertical"
+ android:windowIsFloating="true">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"/>
+
+ <TextView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/launcher_shortcuts_empty"/>
+ </LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/launch_app_listitem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:padding="6dip"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/favicon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="6dip"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"/>
+ </LinearLayout>
--- a/embedding/android/strings.xml.in
+++ b/embedding/android/strings.xml.in
@@ -16,9 +16,12 @@
<string name="crash_help_message">&crash_help_message;</string>
<string name="crash_send_report_message">&crash_send_report_message;</string>
<string name="crash_include_url">&crash_include_url;</string>
<string name="crash_close_label">&crash_close_label;</string>
<string name="crash_restart_label">&crash_restart_label;</string>
<string name="sending_crash_report">&sending_crash_report;</string>
<string name="exit_label">&exit_label;</string>
<string name="continue_label">&continue_label;</string>
+
+ <string name="launcher_shortcuts_title">&launcher_shortcuts_title;</string>
+ <string name="launcher_shortcuts_empty">&launcher_shortcuts_empty;</string>
</resources>