BluetoothOppNotification.java revision 1ac5507790a87810061a19dadec36eb328a222ea
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.opp; 34 35import com.android.bluetooth.R; 36 37import android.content.Context; 38import android.app.Notification; 39import android.app.NotificationManager; 40import android.app.PendingIntent; 41import android.content.Intent; 42import android.database.Cursor; 43import android.net.Uri; 44import android.util.Log; 45import android.widget.RemoteViews; 46import android.os.Process; 47import java.util.HashMap; 48 49/** 50 * This class handles the updating of the Notification Manager for the cases 51 * where there is an ongoing transfer, incoming transfer need confirm and 52 * complete (successful or failed) transfer. 53 */ 54class BluetoothOppNotification { 55 private static final String TAG = "BluetoothOppNotification"; 56 57 static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")"; 58 59 static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR " 60 + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")"; 61 62 static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '" 63 + BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR " 64 + BluetoothShare.USER_CONFIRMATION + " == '" 65 + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "'" + ")"; 66 67 static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm; 68 69 static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible; 70 71 static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '" 72 + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible; 73 74 public NotificationManager mNotificationMgr; 75 76 private Context mContext; 77 78 private HashMap<String, NotificationItem> mNotifications; 79 80 private NotificationUpdateThread mUpdateNotificationThread; 81 82 private boolean mPendingUpdate = false; 83 84 private boolean mFinised = false; 85 86 /** 87 * This inner class is used to describe some properties for one transfer. 88 */ 89 static class NotificationItem { 90 int id; // This first field _id in db; 91 92 int direction; // to indicate sending or receiving 93 94 int totalCurrent = 0; // current transfer bytes 95 96 int totalTotal = 0; // total bytes for current transfer 97 98 String description; // the text above progress bar 99 } 100 101 /** 102 * Constructor 103 * 104 * @param ctx The context to use to obtain access to the Notification 105 * Service 106 */ 107 BluetoothOppNotification(Context ctx) { 108 mContext = ctx; 109 mNotificationMgr = (NotificationManager)mContext 110 .getSystemService(Context.NOTIFICATION_SERVICE); 111 mNotifications = new HashMap<String, NotificationItem>(); 112 } 113 114 public void finishNotification() { 115 synchronized (this) { 116 mFinised = true; 117 } 118 } 119 120 /** 121 * Update the notification ui. 122 */ 123 public void updateNotification() { 124 synchronized (this) { 125 mPendingUpdate = true; 126 if (mUpdateNotificationThread == null) { 127 mUpdateNotificationThread = new NotificationUpdateThread(); 128 mUpdateNotificationThread.start(); 129 mFinised = false; 130 } 131 } 132 } 133 134 private class NotificationUpdateThread extends Thread { 135 136 public NotificationUpdateThread() { 137 super("Notification Update Thread"); 138 } 139 140 @Override 141 public void run() { 142 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 143 for (;;) { 144 synchronized (this) { 145 if (mUpdateNotificationThread != this) { 146 throw new IllegalStateException( 147 "multiple UpdateThreads in BluetoothOppNotification"); 148 } 149 if (!mPendingUpdate && mFinised) { 150 mUpdateNotificationThread = null; 151 return; 152 } 153 mPendingUpdate = false; 154 } 155 updateActiveNotification(); 156 updateCompletedNotification(); 157 updateIncomingFileConfirmNotification(); 158 } 159 } 160 } 161 162 private void updateActiveNotification() { 163 // Active transfers 164 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 165 WHERE_RUNNING, null, BluetoothShare._ID); 166 if (cursor == null) { 167 return; 168 } 169 170 // Collate the notifications 171 mNotifications.clear(); 172 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 173 int timeStamp = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 174 int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)); 175 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 176 int total = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)); 177 int current = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)); 178 179 String fileName = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)); 180 if (fileName == null) { 181 fileName = cursor.getString(cursor 182 .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 183 } 184 if (fileName == null) { 185 fileName = mContext.getString(R.string.unknown_file); 186 } 187 188 String batchID = Long.toString(timeStamp); 189 190 // sending objects in one batch has same timeStamp 191 if (mNotifications.containsKey(batchID)) { 192 // NOTE: currently no such case 193 // Batch sending case 194 } else { 195 NotificationItem item = new NotificationItem(); 196 item.id = id; 197 item.direction = dir; 198 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 199 item.description = mContext.getString(R.string.notification_sending, fileName); 200 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 201 item.description = mContext 202 .getString(R.string.notification_receiving, fileName); 203 } else { 204 if (Constants.LOGVV) { 205 Log.v(TAG, "mDirection ERROR!"); 206 } 207 } 208 item.totalCurrent = current; 209 item.totalTotal = total; 210 211 mNotifications.put(batchID, item); 212 213 if (Constants.LOGVV) { 214 Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent" 215 + item.totalCurrent + "; totalTotal=" + item.totalTotal); 216 } 217 } 218 } 219 cursor.close(); 220 221 // Add the notifications 222 for (NotificationItem item : mNotifications.values()) { 223 // Build the RemoteView object 224 RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME, 225 R.layout.status_bar_ongoing_event_progress_bar); 226 227 expandedView.setTextViewText(R.id.description, item.description); 228 229 expandedView.setProgressBar(R.id.progress_bar, item.totalTotal, item.totalCurrent, 230 item.totalTotal == -1); 231 232 expandedView.setTextViewText(R.id.progress_text, BluetoothOppUtility 233 .formatProgressText(item.totalTotal, item.totalCurrent)); 234 235 // Build the notification object 236 Notification n = new Notification(); 237 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 238 n.icon = android.R.drawable.stat_sys_upload; 239 expandedView.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_upload); 240 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 241 n.icon = android.R.drawable.stat_sys_download; 242 expandedView.setImageViewResource(R.id.appIcon, 243 android.R.drawable.stat_sys_download); 244 } else { 245 if (Constants.LOGVV) { 246 Log.v(TAG, "mDirection ERROR!"); 247 } 248 } 249 250 n.flags |= Notification.FLAG_ONGOING_EVENT; 251 n.contentView = expandedView; 252 253 Intent intent = new Intent(Constants.ACTION_LIST); 254 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 255 intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); 256 257 n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 258 mNotificationMgr.notify(item.id, n); 259 } 260 } 261 262 private void updateCompletedNotification() { 263 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 264 WHERE_COMPLETED, null, BluetoothShare._ID); 265 if (cursor == null) { 266 return; 267 } 268 269 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 270 // Add the notifications 271 int timeStamp = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 272 int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)); 273 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 274 int status = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)); 275 276 String fileName = cursor.getString(cursor 277 .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 278 if (fileName == null) { 279 fileName = mContext.getString(R.string.unknown_file); 280 } 281 282 String title; 283 String caption; 284 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 285 286 Notification n = new Notification(); 287 if (BluetoothShare.isStatusError(status)) { 288 if (dir == BluetoothShare.DIRECTION_OUTBOUND) { 289 title = mContext.getString(R.string.notification_sent_fail, fileName); 290 } else { 291 title = mContext.getString(R.string.notification_received_fail, fileName); 292 } 293 caption = mContext.getString(R.string.download_fail_line3, BluetoothOppUtility 294 .getStatusDescription(mContext, status)); 295 n.icon = android.R.drawable.stat_notify_error; 296 } else { 297 if (dir == BluetoothShare.DIRECTION_OUTBOUND) { 298 title = mContext.getString(R.string.notification_sent, fileName); 299 n.icon = android.R.drawable.stat_sys_upload_done; 300 } else { 301 title = mContext.getString(R.string.notification_received, fileName); 302 n.icon = android.R.drawable.stat_sys_download_done; 303 } 304 caption = mContext.getString(R.string.notification_sent_complete); 305 } 306 Intent intent = new Intent(Constants.ACTION_OPEN); 307 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 308 intent.setData(contentUri); 309 310 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, 311 intent, 0)); 312 313 intent = new Intent(Constants.ACTION_HIDE); 314 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 315 intent.setData(contentUri); 316 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 317 318 n.when = timeStamp; 319 320 mNotificationMgr.notify(id, n); 321 } 322 cursor.close(); 323 } 324 325 private void updateIncomingFileConfirmNotification() { 326 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 327 WHERE_CONFIRM_PENDING, null, BluetoothShare._ID); 328 329 if (cursor == null) { 330 return; 331 } 332 333 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 334 String title = mContext.getString(R.string.incoming_file_confirm_Notification_title); 335 String caption = mContext 336 .getString(R.string.incoming_file_confirm_Notification_caption); 337 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 338 int timeStamp = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 339 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 340 341 Notification n = new Notification(); 342 n.icon = R.drawable.bt_incomming_file_notification; 343 n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; 344 n.defaults = Notification.DEFAULT_SOUND; 345 n.tickerText = title; 346 Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); 347 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 348 intent.setData(contentUri); 349 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, 350 intent, 0)); 351 352 intent = new Intent(Constants.ACTION_HIDE); 353 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 354 intent.setData(contentUri); 355 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 356 357 n.when = timeStamp; 358 mNotificationMgr.notify(id, n); 359 } 360 cursor.close(); 361 } 362} 363