1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/NotificationClient.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,209 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import android.app.Notification; 1.12 +import android.app.PendingIntent; 1.13 +import android.text.TextUtils; 1.14 +import android.util.Log; 1.15 + 1.16 +import java.util.LinkedList; 1.17 +import java.util.concurrent.ConcurrentHashMap; 1.18 + 1.19 +/** 1.20 + * Client for posting notifications through a NotificationHandler. 1.21 + */ 1.22 +public abstract class NotificationClient { 1.23 + private static final String LOGTAG = "GeckoNotificationClient"; 1.24 + 1.25 + private volatile NotificationHandler mHandler; 1.26 + private boolean mReady; 1.27 + private final LinkedList<Runnable> mTaskQueue = new LinkedList<Runnable>(); 1.28 + private final ConcurrentHashMap<Integer, UpdateRunnable> mUpdatesMap = 1.29 + new ConcurrentHashMap<Integer, UpdateRunnable>(); 1.30 + 1.31 + /** 1.32 + * Runnable that is reused between update notifications. 1.33 + * 1.34 + * Updates happen frequently, so reusing Runnables prevents frequent dynamic allocation. 1.35 + */ 1.36 + private class UpdateRunnable implements Runnable { 1.37 + private long mProgress; 1.38 + private long mProgressMax; 1.39 + private String mAlertText; 1.40 + final private int mNotificationID; 1.41 + 1.42 + public UpdateRunnable(int notificationID) { 1.43 + mNotificationID = notificationID; 1.44 + } 1.45 + 1.46 + public synchronized boolean updateProgress(long progress, long progressMax, String alertText) { 1.47 + if (progress == mProgress 1.48 + && mProgressMax == progressMax 1.49 + && TextUtils.equals(mAlertText, alertText)) { 1.50 + return false; 1.51 + } 1.52 + 1.53 + mProgress = progress; 1.54 + mProgressMax = progressMax; 1.55 + mAlertText = alertText; 1.56 + return true; 1.57 + } 1.58 + 1.59 + @Override 1.60 + public void run() { 1.61 + long progress; 1.62 + long progressMax; 1.63 + String alertText; 1.64 + 1.65 + synchronized (this) { 1.66 + progress = mProgress; 1.67 + progressMax = mProgressMax; 1.68 + alertText = mAlertText; 1.69 + } 1.70 + 1.71 + mHandler.update(mNotificationID, progress, progressMax, alertText); 1.72 + } 1.73 + }; 1.74 + 1.75 + /** 1.76 + * Adds a notification. 1.77 + * 1.78 + * @see NotificationHandler#add(int, String, String, String, PendingIntent, PendingIntent) 1.79 + */ 1.80 + public synchronized void add(final int notificationID, final String aImageUrl, 1.81 + final String aAlertTitle, final String aAlertText, final PendingIntent contentIntent) { 1.82 + mTaskQueue.add(new Runnable() { 1.83 + @Override 1.84 + public void run() { 1.85 + mHandler.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent); 1.86 + } 1.87 + }); 1.88 + notify(); 1.89 + 1.90 + if (!mReady) { 1.91 + bind(); 1.92 + } 1.93 + } 1.94 + 1.95 + /** 1.96 + * Adds a notification. 1.97 + * 1.98 + * @see NotificationHandler#add(int, Notification) 1.99 + */ 1.100 + public synchronized void add(final int notificationID, final Notification notification) { 1.101 + mTaskQueue.add(new Runnable() { 1.102 + @Override 1.103 + public void run() { 1.104 + mHandler.add(notificationID, notification); 1.105 + } 1.106 + }); 1.107 + notify(); 1.108 + 1.109 + if (!mReady) { 1.110 + bind(); 1.111 + } 1.112 + } 1.113 + 1.114 + /** 1.115 + * Updates a notification. 1.116 + * 1.117 + * @see NotificationHandler#update(int, long, long, String) 1.118 + */ 1.119 + public void update(final int notificationID, final long aProgress, final long aProgressMax, 1.120 + final String aAlertText) { 1.121 + UpdateRunnable runnable = mUpdatesMap.get(notificationID); 1.122 + 1.123 + if (runnable == null) { 1.124 + runnable = new UpdateRunnable(notificationID); 1.125 + mUpdatesMap.put(notificationID, runnable); 1.126 + } 1.127 + 1.128 + // If we've already posted an update with these values, there's no 1.129 + // need to do it again. 1.130 + if (!runnable.updateProgress(aProgress, aProgressMax, aAlertText)) { 1.131 + return; 1.132 + } 1.133 + 1.134 + synchronized (this) { 1.135 + if (mReady) { 1.136 + mTaskQueue.add(runnable); 1.137 + notify(); 1.138 + } 1.139 + } 1.140 + } 1.141 + 1.142 + /** 1.143 + * Removes a notification. 1.144 + * 1.145 + * @see NotificationHandler#remove(int) 1.146 + */ 1.147 + public synchronized void remove(final int notificationID) { 1.148 + if (!mReady) { 1.149 + return; 1.150 + } 1.151 + 1.152 + mTaskQueue.add(new Runnable() { 1.153 + @Override 1.154 + public void run() { 1.155 + mHandler.remove(notificationID); 1.156 + mUpdatesMap.remove(notificationID); 1.157 + } 1.158 + }); 1.159 + notify(); 1.160 + } 1.161 + 1.162 + /** 1.163 + * Determines whether a notification is showing progress. 1.164 + * 1.165 + * @see NotificationHandler#isProgressStyle(int) 1.166 + */ 1.167 + public boolean isOngoing(int notificationID) { 1.168 + final NotificationHandler handler = mHandler; 1.169 + return handler != null && handler.isOngoing(notificationID); 1.170 + } 1.171 + 1.172 + protected void bind() { 1.173 + mReady = true; 1.174 + } 1.175 + 1.176 + protected void unbind() { 1.177 + mReady = false; 1.178 + mUpdatesMap.clear(); 1.179 + } 1.180 + 1.181 + protected void connectHandler(NotificationHandler handler) { 1.182 + mHandler = handler; 1.183 + new Thread(new NotificationRunnable()).start(); 1.184 + } 1.185 + 1.186 + private class NotificationRunnable implements Runnable { 1.187 + @Override 1.188 + public void run() { 1.189 + Runnable r; 1.190 + try { 1.191 + while (true) { 1.192 + // Synchronize polls to prevent tasks from being added to the queue 1.193 + // during the isDone check. 1.194 + synchronized (NotificationClient.this) { 1.195 + r = mTaskQueue.poll(); 1.196 + while (r == null) { 1.197 + if (mHandler.isDone()) { 1.198 + unbind(); 1.199 + return; 1.200 + } 1.201 + NotificationClient.this.wait(); 1.202 + r = mTaskQueue.poll(); 1.203 + } 1.204 + } 1.205 + r.run(); 1.206 + } 1.207 + } catch (InterruptedException e) { 1.208 + Log.e(LOGTAG, "Notification task queue processing interrupted", e); 1.209 + } 1.210 + } 1.211 + } 1.212 +}