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