Bug 592088 - support progress indicators in android status bar notifications r=blassey a=blocking-fennec
authorAlex Pakhotin <alexp@mozilla.com>
Fri, 01 Oct 2010 14:21:21 -0700
changeset 54906 a3eadf884f811deccb9e982214cbbcb3d30f9321
parent 54905 7e10dcf5f763f52d64ade35a3d0a355efbcc1cb4
child 54907 f88f8337cef1e5555499f1994dea5edae1d0b1c1
push id16070
push userblassey@mozilla.com
push dateMon, 04 Oct 2010 17:03:27 +0000
treeherdermozilla-central@a3eadf884f81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, blocking-fennec
bugs592088
milestone2.0b7pre
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 592088 - support progress indicators in android status bar notifications r=blassey a=blocking-fennec
embedding/android/AlertNotification.java
embedding/android/GeckoAppShell.java
embedding/android/Makefile.in
embedding/android/NotificationHandler.java.in
embedding/android/resources/layout/notification_progress.xml
embedding/android/resources/layout/notification_progress_text.xml
toolkit/components/alerts/public/nsIAlertsService.idl
toolkit/components/alerts/src/nsAlertsService.cpp
toolkit/components/alerts/src/nsAlertsService.h
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
new file mode 100644
--- /dev/null
+++ b/embedding/android/AlertNotification.java
@@ -0,0 +1,106 @@
+/* -*- Mode: Java; 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 Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Alex Pakhotin <alexp@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;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.widget.RemoteViews;
+
+public class AlertNotification
+    extends Notification
+{
+    Context mContext;
+    int mId;
+    int mIcon;
+    String mTitle;
+    boolean mProgressStyle;
+    NotificationManager mNotificationManager;
+
+    public AlertNotification(Context aContext, int aNotificationId, int aIcon, String aTitle, long aWhen) {
+        super(aIcon, aTitle, aWhen);
+
+        mContext = aContext;
+        mIcon = aIcon;
+        mTitle = aTitle;
+        mProgressStyle = false;
+        mId = aNotificationId;
+
+        mNotificationManager = (NotificationManager)
+            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public boolean isProgressStyle() {
+        return mProgressStyle;
+    }
+
+    public void show() {
+        mNotificationManager.notify(mId, this);
+    }
+
+    public void updateProgress(String aAlertText, long aProgress, long aProgressMax) {
+        if (!mProgressStyle) {
+            // Custom view
+            int layout = aAlertText.length() > 0 ? R.layout.notification_progress_text : R.layout.notification_progress;
+
+            RemoteViews view = new RemoteViews("org.mozilla." + GeckoApp.mAppContext.getAppName(), layout);
+            view.setImageViewResource(R.id.notificationImage, mIcon);
+            view.setTextViewText(R.id.notificationTitle, mTitle);
+            contentView = view;
+            flags |= FLAG_ONGOING_EVENT;
+
+            mProgressStyle = true;
+        }
+
+        String text;
+        if (aAlertText.length() > 0) {
+            text = aAlertText;
+        } else {
+            int percent = 0;
+            if (aProgressMax > 0)
+                percent = (int)(100 * aProgress / aProgressMax);
+            text = percent + "%";
+        }
+
+        contentView.setTextViewText(R.id.notificationText, text);
+        contentView.setProgressBar(R.id.notificationProgressbar, (int)aProgressMax, (int)aProgress, false);
+
+        // Update the notification
+        mNotificationManager.notify(mId, this);
+    }
+}
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
 import java.io.*;
 import java.util.*;
 import java.util.zip.*;
 import java.nio.*;
+import java.lang.reflect.*;
 
 import android.os.*;
 import android.app.*;
 import android.text.*;
 import android.view.*;
 import android.view.inputmethod.*;
 import android.content.*;
 import android.graphics.*;
@@ -68,16 +69,18 @@ class GeckoAppShell
     // static members only
     private GeckoAppShell() { }
 
     static boolean sGeckoRunning;
 
     static private boolean gRestartScheduled = false;
 
     static private final Timer mIMETimer = new Timer();
+    static private final HashMap<Integer, AlertNotification>
+         mAlertNotifications = new HashMap<Integer, AlertNotification>();
 
     static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
     static private final int NOTIFY_IME_SETOPENSTATE = 1;
     static private final int NOTIFY_IME_SETENABLED = 2;
     static private final int NOTIFY_IME_CANCELCOMPOSITION = 3;
     static private final int NOTIFY_IME_FOCUSCHANGE = 4;
 
     /* The Android-side API: API methods that Android calls */
@@ -462,76 +465,111 @@ class GeckoAppShell
 
     static void setClipboardText(String text) {
         Context context = GeckoApp.surfaceView.getContext();
         ClipboardManager cm = (ClipboardManager)
             context.getSystemService(Context.CLIPBOARD_SERVICE);
         cm.setText(text);
     }
 
-    static void showAlertNotification(String imageUrl, String alertTitle, String alertText,
-                                      String alertCookie, String alertName) {
+    public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
+                                             String aAlertCookie, String aAlertName) {
         Log.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
-              "- image = '" + imageUrl + "'\n" +
-              "- title = '" + alertTitle + "'\n" +
-              "- text = '" + alertText +"'\n" +
-              "- cookie = '" + alertCookie +"'\n" +
-              "- name = '" + alertName + "'");
+            "- image = '" + aImageUrl + "'\n" +
+            "- title = '" + aAlertTitle + "'\n" +
+            "- text = '" + aAlertText +"'\n" +
+            "- cookie = '" + aAlertCookie +"'\n" +
+            "- name = '" + aAlertName + "'");
 
         int icon = R.drawable.icon; // Just use the app icon by default
 
-        Uri imageUri = Uri.parse(imageUrl);
+        Uri imageUri = Uri.parse(aImageUrl);
         String scheme = imageUri.getScheme();
-
         if ("drawable".equals(scheme)) {
             String resource = imageUri.getSchemeSpecificPart();
-            if ("//alertdownloads".equals(resource))
-                icon = R.drawable.alertdownloads;
-            else if ("//alertaddons".equals(resource))
-                icon = R.drawable.alertaddons;
+            resource = resource.substring(resource.lastIndexOf('/') + 1);
+            try {
+                Class drawableClass = R.drawable.class;
+                Field f = drawableClass.getField(resource);
+                icon = f.getInt(null);
+            } catch (Exception e) {} // just means the resource doesn't exist
         }
 
-        int notificationID = alertName.hashCode();
+        int notificationID = aAlertName.hashCode();
 
-        Notification notification = new Notification(icon, alertTitle, System.currentTimeMillis());
+        // Remove the old notification with the same ID, if any
+        removeNotification(notificationID);
+
+        AlertNotification notification = new AlertNotification(GeckoApp.mAppContext,
+            notificationID, icon, aAlertTitle, System.currentTimeMillis());
 
         // The intent to launch when the user clicks the expanded notification
         Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLICK);
         notificationIntent.setClassName(GeckoApp.mAppContext,
-                                        "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
+            "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
 
         // Put the strings into the intent as an URI "alert:<name>#<cookie>"
-        Uri dataUri = Uri.fromParts("alert", alertName, alertCookie);
+        Uri dataUri = Uri.fromParts("alert", aAlertName, aAlertCookie);
         notificationIntent.setData(dataUri);
 
         PendingIntent contentIntent = PendingIntent.getActivity(GeckoApp.mAppContext, 0, notificationIntent, 0);
-        notification.setLatestEventInfo(GeckoApp.mAppContext, alertTitle, alertText, contentIntent);
+        notification.setLatestEventInfo(GeckoApp.mAppContext, aAlertTitle, aAlertText, contentIntent);
 
         // The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
         Intent clearNotificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLEAR);
         clearNotificationIntent.setClassName(GeckoApp.mAppContext,
-                                        "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
+            "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
         clearNotificationIntent.setData(dataUri);
 
         PendingIntent pendingClearIntent = PendingIntent.getActivity(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
         notification.deleteIntent = pendingClearIntent;
 
-        // Show the notification
-        NotificationManager notificationManager = (NotificationManager)
-            GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        notificationManager.notify(notificationID, notification);
+        mAlertNotifications.put(notificationID, notification);
+
+        notification.show();
+
         Log.i("GeckoAppJava", "Created notification ID " + notificationID);
     }
 
-    public static void handleNotification(String action, String alertName, String alertCookie) {
-        if (GeckoApp.ACTION_ALERT_CLICK.equals(action)) {
+    public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
+        Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnProgress\n" +
+            "- name = '" + aAlertName +"', " +
+            "progress = " + aProgress +" / " + aProgressMax + ", text = '" + aAlertText + "'");
+
+        int notificationID = aAlertName.hashCode();
+        AlertNotification notification = mAlertNotifications.get(notificationID);
+        if (notification != null)
+            notification.updateProgress(aAlertText, aProgress, aProgressMax);
+    }
+
+    public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
+        int notificationID = aAlertName.hashCode();
+
+        if (GeckoApp.ACTION_ALERT_CLICK.equals(aAction)) {
             Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
-            callObserver(alertName, "alertclickcallback", alertCookie);
+            callObserver(aAlertName, "alertclickcallback", aAlertCookie);
+
+            AlertNotification notification = mAlertNotifications.get(notificationID);
+            if (notification != null && notification.isProgressStyle()) {
+                // When clicked, keep the notification, if it displays a progress
+                return;
+            }
         }
 
-        Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertfinished)");
-        callObserver(alertName, "alertfinished", alertCookie);
-        removeObserver(alertName);
+        callObserver(aAlertName, "alertfinished", aAlertCookie);
+
+        removeObserver(aAlertName);
+
+        removeNotification(notificationID);
     }
     public static String showFilePicker() {
         return GeckoApp.mAppContext.showFilePicker();
     }
+
+    private static void removeNotification(int notificationID) {
+        mAlertNotifications.remove(notificationID);
+
+        NotificationManager notificationManager = (NotificationManager)
+           GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancel(notificationID);
+    }
+
 }
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -44,16 +44,17 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/ipc/app/defs.mk
 
 JAVAFILES = \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoEvent.java \
   GeckoSurfaceView.java \
   GeckoInputConnection.java \
+  AlertNotification.java \
   $(NULL)
 
 PROCESSEDJAVAFILES = \
   App.java \
   Restarter.java \
   NotificationHandler.java \
   $(NULL)
 
@@ -92,24 +93,19 @@ DIRS = utils
 ifeq ($(MOZ_APP_NAME),fennec)
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_48x48.png
 ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png
 else
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
 ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
 endif
 
-RES_DRAWABLE = \
-  res/drawable/alertaddons.png \
-  res/drawable/alertdownloads.png \
-  $(NULL)
-
-RES_DRAWABLE_HDPI = \
-  res/drawable-hdpi/alertaddons.png \
-  res/drawable-hdpi/alertdownloads.png \
+RES_LAYOUT = \
+  res/layout/notification_progress.xml \
+  res/layout/notification_progress_text.xml
   $(NULL)
 
 NATIVE_LIBS = $(shell cat $(DIST)/bin/dependentlibs.list) libxpcom.so libnssckbi.so libfreebl3.so libmozutils.so
 FULL_LIBS = $(addprefix libs/armeabi/,$(NATIVE_LIBS))
 
 # We'll strip all the libs by default, due to size, but we might
 # want to have a non-stripped version for debugging.  We should
 # really pull out debuginfo and stuff.
@@ -163,28 +159,32 @@ AndroidManifest.xml $(PROCESSEDJAVAFILES
 res/drawable/icon.png: $(MOZ_APP_ICON)
 	$(NSINSTALL) -D res/drawable
 	cp $(ICON_PATH) $@
 
 res/drawable-hdpi/icon.png: $(MOZ_APP_ICON)
 	$(NSINSTALL) -D res/drawable-hdpi
 	cp $(ICON_PATH_HDPI) $@
 
-$(RES_DRAWABLE):
-	$(NSINSTALL) -D res/drawable
-	cp $(topsrcdir)/mobile/app/android/drawable/* res/drawable/
+ifdef MOZ_ANDROID_DRAWABLES
+RES_DRAWABLE = $(addprefix res/drawable/,$(notdir $(MOZ_ANDROID_DRAWABLES)))
 
-$(RES_DRAWABLE_HDPI):
-	$(NSINSTALL) -D res/drawable-hdpi
-	cp $(topsrcdir)/mobile/app/android/drawable-hdpi/* res/drawable-hdpi/
+$(RES_DRAWABLE): $(addprefix $(topsrcdir)/,$(MOZ_ANDROID_DRAWABLES))
+	$(NSINSTALL) -D res/drawable
+	$(NSINSTALL) $^ res/drawable/
+endif
 
-R.java: $(MOZ_APP_ICON) $(RES_DRAWABLE) $(RES_DRAWABLE_HDPI) res/values/strings.xml $(LOCALIZED_STRINGS_XML) AndroidManifest.xml
+$(RES_LAYOUT): $(subst res/,$(srcdir)/resources/,$(RES_LAYOUT))
+	$(NSINSTALL) -D res/layout
+	$(NSINSTALL) $(srcdir)/resources/layout/* res/layout/
+
+R.java: $(MOZ_APP_ICON) $(RES_LAYOUT) $(RES_DRAWABLE) res/values/strings.xml $(LOCALIZED_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_DRAWABLE) $(RES_DRAWABLE_HDPI) res/values/strings.xml $(LOCALIZED_STRINGS_XML)
+gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png $(RES_LAYOUT) $(RES_DRAWABLE) res/values/strings.xml $(LOCALIZED_STRINGS_XML)
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar  -S res -F $@
 
 libs/armeabi/%: $(DIST)/lib/%
 	@$(NSINSTALL) -D libs/armeabi
 	@cp -L -v $< $@
 	@$(DO_STRIP) $@
 
 # Bug 567873 - Android packaging should use standard packaging code
--- a/embedding/android/NotificationHandler.java.in
+++ b/embedding/android/NotificationHandler.java.in
@@ -77,22 +77,23 @@ public class NotificationHandler
               "- action = '" + action + "'\n" +
               "- alertName = '" + alertName + "'\n" +
               "- alertCookie = '" + alertCookie + "'");
 
         int notificationID = alertName.hashCode();
 
         Log.i("GeckoAppJava", "Handle notification ID " + notificationID);
 
-        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        notificationManager.cancel(notificationID);
-
         if (App.mAppContext != null) {
             // This should call the observer, if any
             App.mAppContext.handleNotification(action, alertName, alertCookie);
+        } else {
+            // The app is not running, just cancel this notification
+            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+            notificationManager.cancel(notificationID);
         }
 
         if (App.ACTION_ALERT_CLICK.equals(action)) {
             // Start or bring to front the main activity
             Intent appIntent = new Intent(Intent.ACTION_MAIN);
             appIntent.setClassName(this, "org.mozilla.@MOZ_APP_NAME@.App");
             try {
                 startActivity(appIntent);
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/notification_progress.xml
@@ -0,0 +1,53 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingTop="7dp"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <ImageView android:id="@+id/notificationImage"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/notificationTitle"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:textStyle="bold"
+            android:textSize="18sp"
+            android:paddingLeft="10dp"
+            android:textColor="#ff000000" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <TextView android:id="@+id/notificationText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="3dp"
+            android:textColor="#ff000000" />
+
+        <ProgressBar android:id="@+id/notificationProgressbar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="1dip"
+            android:layout_marginBottom="1dip"
+            android:layout_marginLeft="4dip"
+            android:layout_marginRight="10dip"
+            android:layout_centerHorizontal="true" />
+    </LinearLayout>
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/notification_progress_text.xml
@@ -0,0 +1,46 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <ImageView android:id="@+id/notificationImage"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/notificationTitle"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:textStyle="bold"
+            android:textSize="18sp"
+            android:paddingLeft="4dp"
+            android:textColor="#ff000000" />
+    </LinearLayout>
+
+    <ProgressBar android:id="@+id/notificationProgressbar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="16dip"
+        android:layout_marginTop="1dip"
+        android:layout_marginBottom="1dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_centerHorizontal="true" />
+
+    <TextView android:id="@+id/notificationText"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="4dp"
+        android:textColor="#ff000000" />
+
+</LinearLayout>
--- a/toolkit/components/alerts/public/nsIAlertsService.idl
+++ b/toolkit/components/alerts/public/nsIAlertsService.idl
@@ -73,9 +73,32 @@ interface nsIAlertsService : nsISupports
     */
    void showAlertNotification(in AString  imageUrl, 
                               in AString  title, 
                               in AString  text, 
                               [optional] in boolean textClickable,
                               [optional] in AString cookie,
                               [optional] in nsIObserver alertListener,
                               [optional] in AString name);
+
 };
+
+[scriptable, uuid(df1bd4b0-3a8c-40e6-806a-203f38b0bd9f)]
+interface nsIAlertsProgressListener : nsISupports
+{
+    /**
+     * Called to notify the alert service that progress has occurred for the
+     * given notification previously displayed with showAlertNotification().
+     *
+     * @param name         The name of the notification displaying the
+     *                     progress. On Android the name is hashed and used
+     *                     as a notification ID.
+     * @param progress     Numeric value in the range 0 to progressMax
+     *                     indicating the current progress.
+     * @param progressMax  Numeric value indicating the maximum progress.
+     * @param text         The contents of the alert. If not provided,
+     *                     the percentage will be displayed.
+     */
+    void onProgress(in AString name,
+                    in long long progress,
+                    in long long progressMax,
+                    [optional] in AString text);
+};
--- a/toolkit/components/alerts/src/nsAlertsService.cpp
+++ b/toolkit/components/alerts/src/nsAlertsService.cpp
@@ -62,23 +62,17 @@ using mozilla::dom::ContentChild;
 #include "nsToolkitCompsCID.h"
 
 static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
 
 #define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul"
 
 #endif // !ANDROID
 
-NS_IMPL_THREADSAFE_ADDREF(nsAlertsService)
-NS_IMPL_THREADSAFE_RELEASE(nsAlertsService)
-
-NS_INTERFACE_MAP_BEGIN(nsAlertsService)
-   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService)
-   NS_INTERFACE_MAP_ENTRY(nsIAlertsService)
-NS_INTERFACE_MAP_END_THREADSAFE
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsAlertsService, nsIAlertsService, nsIAlertsProgressListener)
 
 nsAlertsService::nsAlertsService()
 {
 }
 
 nsAlertsService::~nsAlertsService()
 {}
 
@@ -189,8 +183,21 @@ NS_IMETHODIMP nsAlertsService::ShowAlert
   }
 
   rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank",
                  "chrome,dialog=yes,titlebar=no,popup=yes", argsArray,
                  getter_AddRefs(newWindow));
   return rv;
 #endif // !ANDROID
 }
+
+NS_IMETHODIMP nsAlertsService::OnProgress(const nsAString & aAlertName,
+                                          PRInt64 aProgress,
+                                          PRInt64 aProgressMax,
+                                          const nsAString & aAlertText)
+{
+#ifdef ANDROID
+  mozilla::AndroidBridge::Bridge()->AlertsProgressListener_OnProgress(aAlertName, aProgress, aProgressMax, aAlertText);
+  return NS_OK;
+#else
+  return NS_ERROR_NOT_IMPLEMENTED;
+#endif // !ANDROID
+}
--- a/toolkit/components/alerts/src/nsAlertsService.h
+++ b/toolkit/components/alerts/src/nsAlertsService.h
@@ -37,19 +37,21 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsAlertsService_h__
 #define nsAlertsService_h__
 
 #include "nsIAlertsService.h"
 #include "nsCOMPtr.h"
 
-class nsAlertsService : public nsIAlertsService
+class nsAlertsService : public nsIAlertsService,
+                        public nsIAlertsProgressListener
 {
 public:
+  NS_DECL_NSIALERTSPROGRESSLISTENER
   NS_DECL_NSIALERTSSERVICE
   NS_DECL_ISUPPORTS
 
   nsAlertsService();
   virtual ~nsAlertsService();
 
 protected:
 };
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -107,16 +107,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jGetHandlersForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForMimeType", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
     jGetHandlersForProtocol = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForProtocol", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
     jOpenUriExternal = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "openUriExternal", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");
     jGetMimeTypeFromExtension = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMimeTypeFromExtension", "(Ljava/lang/String;)Ljava/lang/String;");
     jMoveTaskToBack = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "moveTaskToBack", "()V");
     jGetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getClipboardText", "()Ljava/lang/String;");
     jSetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setClipboardText", "(Ljava/lang/String;)V");
     jShowAlertNotification = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+    jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jShowFilePicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePicker", "()Ljava/lang/String;");
 
 
     jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext"));
     jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10"));
     jEGLSurfaceImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl"));
     jEGLContextImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLContextImpl"));
     jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl"));
@@ -457,16 +458,32 @@ AndroidBridge::ShowAlertNotification(con
     args[1].l = mJNIEnv->NewString(nsPromiseFlatString(aAlertTitle).get(), aAlertTitle.Length());
     args[2].l = mJNIEnv->NewString(nsPromiseFlatString(aAlertText).get(), aAlertText.Length());
     args[3].l = mJNIEnv->NewString(nsPromiseFlatString(aAlertCookie).get(), aAlertCookie.Length());
     args[4].l = mJNIEnv->NewString(nsPromiseFlatString(aAlertName).get(), aAlertName.Length());
     mJNIEnv->CallStaticVoidMethodA(mGeckoAppShellClass, jShowAlertNotification, args);
 }
 
 void
+AndroidBridge::AlertsProgressListener_OnProgress(const nsAString& aAlertName,
+                                                 PRInt64 aProgress,
+                                                 PRInt64 aProgressMax,
+                                                 const nsAString& aAlertText)
+{
+    ALOG("AlertsProgressListener_OnProgress");
+
+    AutoLocalJNIFrame jniFrame;
+
+    jstring jstrName = mJNIEnv->NewString(nsPromiseFlatString(aAlertName).get(), aAlertName.Length());
+    jstring jstrText = mJNIEnv->NewString(nsPromiseFlatString(aAlertText).get(), aAlertText.Length());
+    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jAlertsProgressListener_OnProgress,
+                                  jstrName, aProgress, aProgressMax, jstrText);
+}
+
+void
 AndroidBridge::ShowFilePicker(nsAString& aFilePath)
 {
     jstring jstr =  static_cast<jstring>(mJNIEnv->CallStaticObjectMethod(
                                              mGeckoAppShellClass, jShowFilePicker));
     aFilePath.Assign(nsJNIString(jstr));
 }
 
 void
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -152,16 +152,21 @@ public:
 
     void ShowAlertNotification(const nsAString& aImageUrl,
                                const nsAString& aAlertTitle,
                                const nsAString& aAlertText,
                                const nsAString& aAlertData,
                                nsIObserver *aAlertListener,
                                const nsAString& aAlertName);
 
+    void AlertsProgressListener_OnProgress(const nsAString& aAlertName,
+                                           PRInt64 aProgress,
+                                           PRInt64 aProgressMax,
+                                           const nsAString& aAlertText);
+
     void ShowFilePicker(nsAString& aFilePath);
 
     struct AutoLocalJNIFrame {
         AutoLocalJNIFrame(int nEntries = 128) : mEntries(nEntries) {
             AndroidBridge::Bridge()->JNI()->PushLocalFrame(mEntries);
         }
         // Note! Calling Purge makes all previous local refs created in
         // the AutoLocalJNIFrame's scope INVALID; be sure that you locked down
@@ -214,16 +219,17 @@ protected:
     jmethodID jGetHandlersForMimeType;
     jmethodID jGetHandlersForProtocol;
     jmethodID jOpenUriExternal;
     jmethodID jGetMimeTypeFromExtension;
     jmethodID jMoveTaskToBack;
     jmethodID jGetClipboardText;
     jmethodID jSetClipboardText;
     jmethodID jShowAlertNotification;
+    jmethodID jAlertsProgressListener_OnProgress;
     jmethodID jShowFilePicker;
 
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;