diff -r 000000000000 -r 6474c204b198 mobile/android/base/NotificationClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/NotificationClient.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,209 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import android.app.Notification; +import android.app.PendingIntent; +import android.text.TextUtils; +import android.util.Log; + +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Client for posting notifications through a NotificationHandler. + */ +public abstract class NotificationClient { + private static final String LOGTAG = "GeckoNotificationClient"; + + private volatile NotificationHandler mHandler; + private boolean mReady; + private final LinkedList mTaskQueue = new LinkedList(); + private final ConcurrentHashMap mUpdatesMap = + new ConcurrentHashMap(); + + /** + * Runnable that is reused between update notifications. + * + * Updates happen frequently, so reusing Runnables prevents frequent dynamic allocation. + */ + private class UpdateRunnable implements Runnable { + private long mProgress; + private long mProgressMax; + private String mAlertText; + final private int mNotificationID; + + public UpdateRunnable(int notificationID) { + mNotificationID = notificationID; + } + + public synchronized boolean updateProgress(long progress, long progressMax, String alertText) { + if (progress == mProgress + && mProgressMax == progressMax + && TextUtils.equals(mAlertText, alertText)) { + return false; + } + + mProgress = progress; + mProgressMax = progressMax; + mAlertText = alertText; + return true; + } + + @Override + public void run() { + long progress; + long progressMax; + String alertText; + + synchronized (this) { + progress = mProgress; + progressMax = mProgressMax; + alertText = mAlertText; + } + + mHandler.update(mNotificationID, progress, progressMax, alertText); + } + }; + + /** + * Adds a notification. + * + * @see NotificationHandler#add(int, String, String, String, PendingIntent, PendingIntent) + */ + public synchronized void add(final int notificationID, final String aImageUrl, + final String aAlertTitle, final String aAlertText, final PendingIntent contentIntent) { + mTaskQueue.add(new Runnable() { + @Override + public void run() { + mHandler.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent); + } + }); + notify(); + + if (!mReady) { + bind(); + } + } + + /** + * Adds a notification. + * + * @see NotificationHandler#add(int, Notification) + */ + public synchronized void add(final int notificationID, final Notification notification) { + mTaskQueue.add(new Runnable() { + @Override + public void run() { + mHandler.add(notificationID, notification); + } + }); + notify(); + + if (!mReady) { + bind(); + } + } + + /** + * Updates a notification. + * + * @see NotificationHandler#update(int, long, long, String) + */ + public void update(final int notificationID, final long aProgress, final long aProgressMax, + final String aAlertText) { + UpdateRunnable runnable = mUpdatesMap.get(notificationID); + + if (runnable == null) { + runnable = new UpdateRunnable(notificationID); + mUpdatesMap.put(notificationID, runnable); + } + + // If we've already posted an update with these values, there's no + // need to do it again. + if (!runnable.updateProgress(aProgress, aProgressMax, aAlertText)) { + return; + } + + synchronized (this) { + if (mReady) { + mTaskQueue.add(runnable); + notify(); + } + } + } + + /** + * Removes a notification. + * + * @see NotificationHandler#remove(int) + */ + public synchronized void remove(final int notificationID) { + if (!mReady) { + return; + } + + mTaskQueue.add(new Runnable() { + @Override + public void run() { + mHandler.remove(notificationID); + mUpdatesMap.remove(notificationID); + } + }); + notify(); + } + + /** + * Determines whether a notification is showing progress. + * + * @see NotificationHandler#isProgressStyle(int) + */ + public boolean isOngoing(int notificationID) { + final NotificationHandler handler = mHandler; + return handler != null && handler.isOngoing(notificationID); + } + + protected void bind() { + mReady = true; + } + + protected void unbind() { + mReady = false; + mUpdatesMap.clear(); + } + + protected void connectHandler(NotificationHandler handler) { + mHandler = handler; + new Thread(new NotificationRunnable()).start(); + } + + private class NotificationRunnable implements Runnable { + @Override + public void run() { + Runnable r; + try { + while (true) { + // Synchronize polls to prevent tasks from being added to the queue + // during the isDone check. + synchronized (NotificationClient.this) { + r = mTaskQueue.poll(); + while (r == null) { + if (mHandler.isDone()) { + unbind(); + return; + } + NotificationClient.this.wait(); + r = mTaskQueue.poll(); + } + } + r.run(); + } + } catch (InterruptedException e) { + Log.e(LOGTAG, "Notification task queue processing interrupted", e); + } + } + } +}