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 idunknown
push userunknown
push dateunknown
reviewersblassey, blocking-fennec
bugs592088
milestone2.0b7pre
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;