Bug 569402 - Show notifications in the Status Bar on Android r=blassey a=blocking-fennec
authorAlex Pakhotin <alexp@mozilla.com>
Mon, 14 Jun 2010 19:17:37 -0700
changeset 51723 8e6af865ffdb7200db3ef9f21df180179c754f8d
parent 51722 3ec56ec77529977a390dee9effbb23f9cedab232
child 51724 b68354fbfb713ad6ff7ced7c63a43469d96e8955
push id15400
push userblassey@mozilla.com
push dateMon, 30 Aug 2010 01:33:35 +0000
treeherdermozilla-central@8e6af865ffdb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, blocking-fennec
bugs569402
milestone2.0b5pre
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 569402 - Show notifications in the Status Bar on Android r=blassey a=blocking-fennec
embedding/android/AndroidManifest.xml.in
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/Makefile.in
embedding/android/NotificationHandler.java.in
toolkit/components/alerts/public/nsIAlertsService.idl
toolkit/components/alerts/src/nsAlertsService.cpp
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJNI.cpp
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/AndroidJavaWrappers.h
widget/src/android/nsAppShell.cpp
widget/src/android/nsAppShell.h
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -56,15 +56,25 @@
             </intent-filter>
 
             <!-- For debugging -->
             <intent-filter>
                 <action android:name="org.mozilla.gecko.DEBUG" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-	<receiver android:enabled="true" android:name="Restarter">
-	  <intent-filter>
+
+        <activity android:name="NotificationHandler"
+                  android:label="@MOZ_APP_DISPLAYNAME@ Notification"
+                  android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="org.mozilla.gecko.ACTION_ALERT_CLICK" />
+                <action android:name="org.mozilla.gecko.ACTION_ALERT_CLEAR" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:enabled="true" android:name="Restarter">
+          <intent-filter>
             <action android:name="org.mozilla.gecko.restart@MOZ_APP_NAME@" />
-	  </intent-filter>
-	</receiver>
+          </intent-filter>
+        </receiver>
     </application>
 </manifest> 
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -52,16 +52,19 @@ import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 
 import android.util.*;
 
 abstract public class GeckoApp
     extends Activity
 {
+    public static final String ACTION_ALERT_CLICK = "org.mozilla.gecko.ACTION_ALERT_CLICK";
+    public static final String ACTION_ALERT_CLEAR = "org.mozilla.gecko.ACTION_ALERT_CLEAR";
+
     public static FrameLayout mainLayout;
     public static GeckoSurfaceView surfaceView;
     public static GeckoApp mAppContext;
     ProgressDialog mProgressDialog;
 
     void launch()
     {
         // unpack files in the components directory
@@ -426,9 +429,13 @@ abstract public class GeckoApp
             String amCmd = "/system/bin/am broadcast -a " + action + getEnvString() + " -n org.mozilla." + getAppName() + "/org.mozilla." + getAppName() + ".Restarter";
             Log.i("GeckoAppJava", amCmd);
             Runtime.getRuntime().exec(amCmd);
         } catch (Exception e) {
             Log.i("GeckoAppJava", e.toString());
         }
         System.exit(0);
     }
+
+    public void handleNotification(String action, String alertName, String alertCookie) {
+        GeckoAppShell.handleNotification(action, alertName, alertCookie);
+    }
 }
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -86,16 +86,18 @@ class GeckoAppShell
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
     public static native void setInitialSize(int width, int height);
     public static native void setSurfaceView(GeckoSurfaceView sv);
     public static native void putenv(String map);
     public static native void onResume();
+    public static native void callObserver(String observerKey, String topic, String data);
+    public static native void removeObserver(String observerKey);
 
     // java-side stuff
     public static void loadGeckoLibs() {
         // The package data lib directory isn't placed in ld.so's
         // search path, so we have to manually load libraries that
         // libxul will depend on.  Not ideal.
 
         // MozAlloc
@@ -434,9 +436,74 @@ 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) {
+        Log.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
+              "- image = '" + imageUrl + "'\n" +
+              "- title = '" + alertTitle + "'\n" +
+              "- text = '" + alertText +"'\n" +
+              "- cookie = '" + alertCookie +"'\n" +
+              "- name = '" + alertName + "'");
+
+        int icon = R.drawable.icon; // Just use the app icon by default
+
+        Uri imageUri = Uri.parse(imageUrl);
+        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;
+        }
+
+        int notificationID = alertName.hashCode();
+
+        Notification notification = new Notification(icon, alertTitle, 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");
+
+        // Put the strings into the intent as an URI "alert:<name>#<cookie>"
+        Uri dataUri = Uri.fromParts("alert", alertName, alertCookie);
+        notificationIntent.setData(dataUri);
+
+        PendingIntent contentIntent = PendingIntent.getActivity(GeckoApp.mAppContext, 0, notificationIntent, 0);
+        notification.setLatestEventInfo(GeckoApp.mAppContext, alertTitle, alertText, 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");
+        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);
+        Log.i("GeckoAppJava", "Created notification ID " + notificationID);
+    }
+
+    public static void handleNotification(String action, String alertName, String alertCookie) {
+        if (GeckoApp.ACTION_ALERT_CLICK.equals(action)) {
+            Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
+            callObserver(alertName, "alertclickcallback", alertCookie);
+        }
+
+        Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertfinished)");
+        callObserver(alertName, "alertfinished", alertCookie);
+        removeObserver(alertName);
+    }
 }
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -46,45 +46,60 @@ include $(topsrcdir)/ipc/app/defs.mk
 JAVAFILES = \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoEvent.java \
   GeckoSurfaceView.java \
   GeckoInputConnection.java \
   $(NULL)
 
+PROCESSEDJAVAFILES = \
+  App.java \
+  Restarter.java \
+  NotificationHandler.java \
+  $(NULL)
+
 DEFINES += \
   -DMOZ_APP_DISPLAYNAME=$(MOZ_APP_DISPLAYNAME) \
   -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
   -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
 
 GARBAGE += \
   AndroidManifest.xml  \
   classes.dex  \
   $(MOZ_APP_NAME).apk  \
-  App.java \
-  Restarter.java \
+  $(PROCESSEDJAVAFILES) \
   gecko.ap_  \
   gecko-unaligned.apk  \
   gecko-unsigned-unaligned.apk \
   $(NULL)
 
 GARBAGE_DIRS += res libs dist classes
 
 DIRS = utils
 
 # Bug 567884 - Need a way to find appropriate icons during packaging
 ifeq ($(MOZ_APP_NAME),fennec)
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_48x48.png
-ICON_PATH_HI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png
+ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png
 else
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
-ICON_PATH_HI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.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 \
+  $(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.
 ifdef NO_STRIP
 DO_STRIP=echo not stripping
@@ -115,34 +130,45 @@ include $(topsrcdir)/config/rules.mk
 
 # Override the Java settings with some specific android settings
 include $(topsrcdir)/config/android-common.mk
 
 tools:: $(MOZ_APP_NAME).apk
 
 # Note that we're going to set up a dependency directly between embed_android.dex and the java files
 # Instead of on the .class files, since more than one .class file might be produced per .java file
-classes.dex: $(JAVAFILES) App.java Restarter.java
+classes.dex: $(JAVAFILES) $(PROCESSEDJAVAFILES) R.java
 	$(NSINSTALL) -D classes
-	$(JAVAC) $(JAVAC_FLAGS) -d classes  $(addprefix $(srcdir)/,$(JAVAFILES)) App.java Restarter.java
+	$(JAVAC) $(JAVAC_FLAGS) -d classes  $(addprefix $(srcdir)/,$(JAVAFILES)) $(PROCESSEDJAVAFILES) R.java
 	$(DX) --dex --output=$@ classes
 
-AndroidManifest.xml App.java Restarter.java : % : %.in
+AndroidManifest.xml $(PROCESSEDJAVAFILES): % : %.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
              $(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
 
 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_HI) $@
+	cp $(ICON_PATH_HDPI) $@
+
+$(RES_DRAWABLE):
+	$(NSINSTALL) -D res/drawable
+	cp $(topsrcdir)/mobile/app/android/drawable/* res/drawable/
 
-gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png
+$(RES_DRAWABLE_HDPI):
+	$(NSINSTALL) -D res/drawable-hdpi
+	cp $(topsrcdir)/mobile/app/android/drawable-hdpi/* res/drawable-hdpi/
+
+R.java: $(MOZ_APP_ICON) $(RES_DRAWABLE) $(RES_DRAWABLE_HDPI)
+	$(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)
 	$(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
new file mode 100644
--- /dev/null
+++ b/embedding/android/NotificationHandler.java.in
@@ -0,0 +1,104 @@
+/* -*- 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 ***** */
+
+#filter substitution
+package org.mozilla.@MOZ_APP_NAME@;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.ActivityNotFoundException;
+import android.os.Bundle;
+import android.util.Log;
+import android.net.Uri;
+
+public class NotificationHandler
+    extends Activity
+{
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i("GeckoAppJava", "NotificationHandler.onCreate");
+
+        Intent intent = getIntent();
+        if (intent != null)
+            handleIntent(intent);
+
+        finish();
+    }
+
+    protected void handleIntent(Intent notificationIntent) {
+        String action = notificationIntent.getAction();
+        String alertName = "";
+        String alertCookie = "";
+        Uri data = notificationIntent.getData();
+        if (data != null) {
+            alertName = data.getSchemeSpecificPart();
+            alertCookie = data.getFragment();
+            if (alertCookie == null)
+                alertCookie = "";
+        }
+
+        Log.i("GeckoAppJava", "NotificationHandler.handleIntent\n" +
+              "- 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);
+        }
+
+        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);
+            } catch (ActivityNotFoundException e) {
+                Log.e("GeckoAppJava", "NotificationHandler Exception: " + e.getMessage());
+            }
+        }
+    }
+}
--- a/toolkit/components/alerts/public/nsIAlertsService.idl
+++ b/toolkit/components/alerts/public/nsIAlertsService.idl
@@ -51,19 +51,21 @@ interface nsIAlertsService : nsISupports
     * @param text           The contents of the alert.
     * @param textClickable  If true, causes the alert text to look like a link
     *                       and notifies the listener when user attempts to 
     *                       click the alert text.
     * @param cookie         A blind cookie the alert will pass back to the 
     *                       consumer during the alert listener callbacks.
     * @param alertListener  Used for callbacks. May be null if the caller 
     *                       doesn't care about callbacks.
-    * @param name           The name of the notification.  This is currently
-    *                       only used on OS X with Growl.  On OS X with Growl,
-    *                       users can disable notifications with a given name.
+    * @param name           The name of the notification. This is currently
+    *                       only used on OS X with Growl and Android.
+    *                       On OS X with Growl, users can disable notifications
+    *                       with a given name. On Android the name is hashed
+    *                       and used as a notification ID.
     *
     * @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
     *
     * The following arguments will be passed to the alertListener's observe() 
     * method:
     *   subject - null
     *   topic   - "alertfinished" when the alert goes away
     *             "alertclickcallback" when the text is clicked
--- a/toolkit/components/alerts/src/nsAlertsService.cpp
+++ b/toolkit/components/alerts/src/nsAlertsService.cpp
@@ -17,47 +17,55 @@
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2002
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Scott MacGregor <mscott@netscape.com>
  *   Jens Bannmann <jens.b@web.de>
+ *   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 ***** */
 
 #include "nsAlertsService.h"
+
+#ifdef ANDROID
+#include "AndroidBridge.h"
+#else
+
 #include "nsISupportsArray.h"
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIServiceManager.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIWindowWatcher.h"
 #include "nsDependentString.h"
 #include "nsWidgetsCID.h"
 #include "nsILookAndFeel.h"
 #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
 
@@ -69,16 +77,21 @@ nsAlertsService::~nsAlertsService()
 {}
 
 NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle, 
                                                      const nsAString & aAlertText, PRBool aAlertTextClickable,
                                                      const nsAString & aAlertCookie,
                                                      nsIObserver * aAlertListener,
                                                      const nsAString & aAlertName)
 {
+#ifdef ANDROID
+  mozilla::AndroidBridge::Bridge()->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertCookie,
+                                                          aAlertListener, aAlertName);
+  return NS_OK;
+#else
   // Check if there is an optional service that handles system-level notifications
   nsCOMPtr<nsIAlertsService> sysAlerts(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID));
   nsresult rv;
   if (sysAlerts) {
     rv = sysAlerts->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable,
                                           aAlertCookie, aAlertListener, aAlertName);
     if (NS_SUCCEEDED(rv))
       return rv;
@@ -151,9 +164,10 @@ NS_IMETHODIMP nsAlertsService::ShowAlert
     rv = argsArray->AppendElement(ifptr);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank",
                  "chrome,dialog=yes,titlebar=no,popup=yes", argsArray,
                  getter_AddRefs(newWindow));
   return rv;
+#endif // !ANDROID
 }
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -41,16 +41,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "nsXULAppAPI.h"
 #endif
 #include <pthread.h>
 #include <prthread.h>
 #include "nsXPCOMStrings.h"
 
 #include "AndroidBridge.h"
+#include "nsAppShell.h"
 
 using namespace mozilla;
 
 static PRUintn sJavaEnvThreadIndex = 0;
 
 AndroidBridge *AndroidBridge::sBridge = 0;
 
 static void
@@ -105,16 +106,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jNotifyXreExit = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "onXreExit", "()V");
     jGetHandlersForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForMimeType", "(Ljava/lang/String;)[Ljava/lang/String;");
     jGetHandlersForProtocol = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForProtocol", "(Ljava/lang/String;)[Ljava/lang/String;");
     jOpenUriExternal = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "openUriExternal", "(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");
 
 
     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"));
     jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl"));
@@ -399,16 +401,40 @@ AndroidBridge::ClipboardHasText()
 
 void
 AndroidBridge::EmptyClipboard()
 {
     mJNIEnv->CallStaticObjectMethod(mGeckoAppShellClass, jSetClipboardText, nsnull);
 }
 
 void
+AndroidBridge::ShowAlertNotification(const nsAString& aImageUrl,
+                                     const nsAString& aAlertTitle,
+                                     const nsAString& aAlertText,
+                                     const nsAString& aAlertCookie,
+                                     nsIObserver *aAlertListener,
+                                     const nsAString& aAlertName)
+{
+    ALOG("ShowAlertNotification");
+
+    AutoLocalJNIFrame jniFrame;
+
+    if (nsAppShell::gAppShell && aAlertListener)
+        nsAppShell::gAppShell->AddObserver(aAlertName, aAlertListener);
+
+    jvalue args[5];
+    args[0].l = mJNIEnv->NewString(nsPromiseFlatString(aImageUrl).get(), aImageUrl.Length());
+    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::SetSurfaceView(jobject obj)
 {
     mSurfaceView.Init(obj);
 }
 
 void *
 AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView &sview)
 {
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -38,16 +38,17 @@
 #ifndef AndroidBridge_h__
 #define AndroidBridge_h__
 
 #include <jni.h>
 #include <android/log.h>
 
 #include "nsCOMPtr.h"
 #include "nsIRunnable.h"
+#include "nsIObserver.h"
 
 #include "AndroidJavaWrappers.h"
 
 #include "nsVoidArray.h"
 
 // Some debug #defines
 // #define ANDROID_DEBUG_EVENTS
 // #define ANDROID_DEBUG_WIDGET
@@ -129,16 +130,23 @@ public:
     bool GetClipboardText(nsAString& aText);
 
     void SetClipboardText(const nsAString& aText);
     
     void EmptyClipboard();
 
     bool ClipboardHasText();
 
+    void ShowAlertNotification(const nsAString& aImageUrl,
+                               const nsAString& aAlertTitle,
+                               const nsAString& aAlertText,
+                               const nsAString& aAlertData,
+                               nsIObserver *aAlertListener,
+                               const nsAString& aAlertName);
+
     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
         // any local refs that you need to keep around in global refs!
         void Purge() {
@@ -186,16 +194,17 @@ protected:
     jmethodID jGetOutstandingDrawEvents;
     jmethodID jGetHandlersForMimeType;
     jmethodID jGetHandlersForProtocol;
     jmethodID jOpenUriExternal;
     jmethodID jGetMimeTypeFromExtension;
     jmethodID jMoveTaskToBack;
     jmethodID jGetClipboardText;
     jmethodID jSetClipboardText;
+    jmethodID jShowAlertNotification;
 
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;
     jclass jEGL10Class;
--- a/widget/src/android/AndroidJNI.cpp
+++ b/widget/src/android/AndroidJNI.cpp
@@ -31,16 +31,17 @@
  * 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 ***** */
 
 #include "nsILocalFile.h"
+#include "nsString.h"
 
 #include "AndroidBridge.h"
 
 #include <jni.h>
 #include <pthread.h>
 #include <dlfcn.h>
 
 #include "nsAppShell.h"
@@ -52,16 +53,18 @@ using namespace mozilla;
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setInitialSize(JNIEnv *jenv, jclass, int width, int height);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey);
 }
 
 
 /*
  * Incoming JNI methods
  */
 
 NS_EXPORT void JNICALL
@@ -93,8 +96,35 @@ Java_org_mozilla_gecko_GeckoAppShell_set
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *jenv, jclass jc)
 {
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->OnResume();
 }
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *jenv, jclass, jstring jObserverKey, jstring jTopic, jstring jData)
+{
+    if (!nsAppShell::gAppShell)
+        return;
+
+    nsJNIString sObserverKey(jObserverKey, jenv);
+    nsJNIString sTopic(jTopic, jenv);
+    nsJNIString sData(jData, jenv);
+
+    nsAppShell::gAppShell->CallObserver(sObserverKey, sTopic, sData);
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey)
+{
+    if (!nsAppShell::gAppShell)
+        return;
+
+    const jchar *observerKey = jenv->GetStringChars(jObserverKey, NULL);
+    nsString sObserverKey(observerKey);
+    sObserverKey.SetLength(jenv->GetStringLength(jObserverKey));
+    jenv->ReleaseStringChars(jObserverKey, observerKey);
+
+    nsAppShell::gAppShell->RemoveObserver(sObserverKey);
+}
--- a/widget/src/android/AndroidJavaWrappers.cpp
+++ b/widget/src/android/AndroidJavaWrappers.cpp
@@ -436,21 +436,22 @@ AndroidRect::Init(JNIEnv *jenv, jobject 
     } else {
         mTop = 0;
         mLeft = 0;
         mRight = 0;
         mBottom = 0;
     }
 }
 
-nsJNIString::nsJNIString(jstring jstr)
+nsJNIString::nsJNIString(jstring jstr, JNIEnv *jenv)
 {
     if (!jstr) {
         SetIsVoid(PR_TRUE);
         return;
     }
-    const jchar* jCharPtr = JNI()->GetStringChars(jstr, false);
-    int len = JNI()->GetStringLength(jstr);
-    nsresult rv;
+    JNIEnv *jni = jenv;
+    if (!jni)
+        jni = JNI();
+    const jchar* jCharPtr = jni->GetStringChars(jstr, false);
+    int len = jni->GetStringLength(jstr);
     Assign(jCharPtr, len);
-    JNI()->ReleaseStringChars(jstr, jCharPtr);
-
+    jni->ReleaseStringChars(jstr, jCharPtr);
 }
--- a/widget/src/android/AndroidJavaWrappers.h
+++ b/widget/src/android/AndroidJavaWrappers.h
@@ -463,14 +463,14 @@ public:
         IME_GET_SELECTION = 6,
         IME_ADD_RANGE = 7
     };
 };
 
 class nsJNIString : public nsString
 {
 public:
-    nsJNIString(jstring jstr);
+    nsJNIString(jstring jstr, JNIEnv *jenv = NULL);
 };
 
 }
 
 #endif
--- a/widget/src/android/nsAppShell.cpp
+++ b/widget/src/android/nsAppShell.cpp
@@ -113,16 +113,18 @@ nsAppShell::Init()
 #endif
 
     mQueueLock = PR_NewLock();
     mCondLock = PR_NewLock();
     mPausedLock = PR_NewLock();
     mQueueCond = PR_NewCondVar(mCondLock);
     mPaused = PR_NewCondVar(mPausedLock);
 
+    mObserversHash.Init();
+
     return nsBaseAppShell::Init();
 }
 
 
 void
 nsAppShell::ScheduleNativeEventCallback()
 {
     EVLOG("nsAppShell::ScheduleNativeEventCallback pth: %p thread: %p main: %d", (void*) pthread_self(), (void*) NS_GetCurrentThread(), NS_IsMainThread());
@@ -362,16 +364,77 @@ void
 nsAppShell::OnResume()
 {
     PR_Lock(mPausedLock);
     PR_NotifyCondVar(mPaused);
     PR_Unlock(mPausedLock);
 
 }
 
+nsresult
+nsAppShell::AddObserver(const nsAString &aObserverKey, nsIObserver *aObserver)
+{
+    NS_ASSERTION(aObserver != nsnull, "nsAppShell::AddObserver: aObserver is null!");
+    return mObserversHash.Put(aObserverKey, aObserver) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+/**
+ * The XPCOM event that will call the observer on the main thread.
+ */
+class ObserverCaller : public nsRunnable {
+public:
+    ObserverCaller(nsIObserver *aObserver, const char *aTopic, const PRUnichar *aData) :
+        mObserver(aObserver), mTopic(aTopic), mData(aData) {
+        NS_ASSERTION(aObserver != nsnull, "ObserverCaller: aObserver is null!");
+    }
+
+    NS_IMETHOD Run() {
+        ALOG("ObserverCaller::Run: observer = %p, topic = '%s')",
+             (nsIObserver*)mObserver, mTopic.get());
+        mObserver->Observe(nsnull, mTopic.get(), mData.get());
+        return NS_OK;
+    }
+
+private:
+    nsCOMPtr<nsIObserver> mObserver;
+    nsCString mTopic;
+    nsString mData;
+};
+
+void
+nsAppShell::CallObserver(const nsAString &aObserverKey, const nsAString &aTopic, const nsAString &aData)
+{
+    nsCOMPtr<nsIObserver> observer;
+    mObserversHash.Get(aObserverKey, getter_AddRefs(observer));
+
+    if (!observer) {
+        ALOG("nsAppShell::CallObserver: Observer was not found!");
+        return;
+    }
+
+    const NS_ConvertUTF16toUTF8 sTopic(aTopic);
+    const nsPromiseFlatString& sData = PromiseFlatString(aData);
+    
+    if (NS_IsMainThread()) {
+        // This branch will unlikely be hit, have it just in case
+        observer->Observe(nsnull, sTopic.get(), sData.get());
+    } else {
+        // Java is not running on main thread, so we have to use NS_DispatchToMainThread
+        nsCOMPtr<nsIRunnable> observerCaller = new ObserverCaller(observer, sTopic.get(), sData.get());
+        nsresult rv = NS_DispatchToMainThread(observerCaller);
+        ALOG("NS_DispatchToMainThread result: %d", rv);
+    }
+}
+
+void
+nsAppShell::RemoveObserver(const nsAString &aObserverKey)
+{
+    mObserversHash.Remove(aObserverKey);
+}
+
 // Used by IPC code
 namespace mozilla {
 
 bool ProcessNextEvent()
 {
     return nsAppShell::gAppShell->ProcessNextNativeEvent(PR_TRUE) ? true : false;
 }
 
--- a/widget/src/android/nsAppShell.h
+++ b/widget/src/android/nsAppShell.h
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsAppShell_h__
 #define nsAppShell_h__
 
 #include "nsBaseAppShell.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
+#include "nsInterfaceHashtable.h"
 
 #include "prcvar.h"
 
 namespace mozilla {
 class AndroidGeckoEvent;
 bool ProcessNextEvent();
 void NotifyEvent();
 }
@@ -65,26 +66,31 @@ public:
     void NotifyNativeEvent();
 
     virtual PRBool ProcessNextNativeEvent(PRBool mayWait);
 
     void PostEvent(mozilla::AndroidGeckoEvent *event);
     void RemoveNextEvent();
     void OnResume();
 
+    nsresult AddObserver(const nsAString &aObserverKey, nsIObserver *aObserver);
+    void CallObserver(const nsAString &aObserverKey, const nsAString &aTopic, const nsAString &aData);
+    void RemoveObserver(const nsAString &aObserverKey);
+
 protected:
     virtual void ScheduleNativeEventCallback();
     virtual ~nsAppShell();
 
     int mNumDraws;
     PRLock *mQueueLock;
     PRLock *mCondLock;
     PRLock *mPausedLock;
     PRCondVar *mQueueCond;
     PRCondVar *mPaused;
     nsTArray<mozilla::AndroidGeckoEvent *> mEventQueue;
+    nsInterfaceHashtable<nsStringHashKey, nsIObserver> mObserversHash;
 
     mozilla::AndroidGeckoEvent *GetNextEvent();
     mozilla::AndroidGeckoEvent *PeekNextEvent();
 };
 
 #endif // nsAppShell_h__