Bug 592088 - support progress indicators in android status bar notifications r=blassey a=blocking-fennec

This commit is contained in:
Alex Pakhotin 2010-10-01 14:21:21 -07:00
parent 6d7e2050b6
commit a0967f88b3
11 changed files with 354 additions and 55 deletions

View File

@ -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);
}
}

View File

@ -41,6 +41,7 @@ import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.nio.*;
import java.lang.reflect.*;
import android.os.*;
import android.app.*;
@ -73,6 +74,8 @@ class GeckoAppShell
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;
@ -467,71 +470,106 @@ class GeckoAppShell
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);
}
}

View File

@ -49,6 +49,7 @@ JAVAFILES = \
GeckoEvent.java \
GeckoSurfaceView.java \
GeckoInputConnection.java \
AlertNotification.java \
$(NULL)
PROCESSEDJAVAFILES = \
@ -97,14 +98,9 @@ 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
@ -168,18 +164,22 @@ res/drawable-hdpi/icon.png: $(MOZ_APP_ICON)
$(NSINSTALL) -D res/drawable-hdpi
cp $(ICON_PATH_HDPI) $@
$(RES_DRAWABLE):
ifdef MOZ_ANDROID_DRAWABLES
RES_DRAWABLE = $(addprefix res/drawable/,$(notdir $(MOZ_ANDROID_DRAWABLES)))
$(RES_DRAWABLE): $(addprefix $(topsrcdir)/,$(MOZ_ANDROID_DRAWABLES))
$(NSINSTALL) -D res/drawable
cp $(topsrcdir)/mobile/app/android/drawable/* res/drawable/
$(NSINSTALL) $^ res/drawable/
endif
$(RES_DRAWABLE_HDPI):
$(NSINSTALL) -D res/drawable-hdpi
cp $(topsrcdir)/mobile/app/android/drawable-hdpi/* res/drawable-hdpi/
$(RES_LAYOUT): $(subst res/,$(srcdir)/resources/,$(RES_LAYOUT))
$(NSINSTALL) -D res/layout
$(NSINSTALL) $(srcdir)/resources/layout/* res/layout/
R.java: $(MOZ_APP_ICON) $(RES_DRAWABLE) $(RES_DRAWABLE_HDPI) res/values/strings.xml $(LOCALIZED_STRINGS_XML) AndroidManifest.xml
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/%

View File

@ -82,12 +82,13 @@ public class NotificationHandler
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)) {

View File

@ -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>

View File

@ -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>

View File

@ -78,4 +78,27 @@ interface nsIAlertsService : nsISupports
[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);
};

View File

@ -67,13 +67,7 @@ static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
#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()
{
@ -194,3 +188,16 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl
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
}

View File

@ -42,9 +42,11 @@
#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

View File

@ -112,6 +112,7 @@ AndroidBridge::Init(JNIEnv *jEnv,
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;");
@ -461,6 +462,22 @@ AndroidBridge::ShowAlertNotification(const nsAString& aImageUrl,
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)
{

View File

@ -157,6 +157,11 @@ public:
nsIObserver *aAlertListener,
const nsAString& aAlertName);
void AlertsProgressListener_OnProgress(const nsAString& aAlertName,
PRInt64 aProgress,
PRInt64 aProgressMax,
const nsAString& aAlertText);
void ShowFilePicker(nsAString& aFilePath);
struct AutoLocalJNIFrame {
@ -219,6 +224,7 @@ protected:
jmethodID jGetClipboardText;
jmethodID jSetClipboardText;
jmethodID jShowAlertNotification;
jmethodID jAlertsProgressListener_OnProgress;
jmethodID jShowFilePicker;
// stuff we need for CallEglCreateWindowSurface