BluetoothOppNotification.java revision 509a6cd43906cce5f9bc78a83bd2058a4e5249e7
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 private static final boolean D = Constants.DEBUG; 57 private static final boolean V = Constants.VERBOSE; 58 59 static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")"; 60 61 static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR " 62 + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")"; 63 64 static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '" 65 + BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR " 66 + BluetoothShare.USER_CONFIRMATION + " == '" 67 + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "'" + ")"; 68 69 static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm; 70 71 static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible; 72 73 private static final String WHERE_COMPLETED_OUTBOUND = WHERE_COMPLETED + " AND " + "(" 74 + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND + ")"; 75 76 private static final String WHERE_COMPLETED_INBOUND = WHERE_COMPLETED + " AND " + "(" 77 + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND + ")"; 78 79 static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '" 80 + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible; 81 82 public NotificationManager mNotificationMgr; 83 84 private Context mContext; 85 86 private HashMap<String, NotificationItem> mNotifications; 87 88 private NotificationUpdateThread mUpdateNotificationThread; 89 90 private boolean mPendingUpdate = false; 91 92 private boolean mFinised = false; 93 94 private static final int NOTIFICATION_ID_OUTBOUND = -1000005; 95 96 private static final int NOTIFICATION_ID_INBOUND = -1000006; 97 98 private boolean mUpdateCompleteNotification = true; 99 100 private int mActiveNotificationId = 0; 101 102 private Notification mOutNoti = null; 103 104 private Notification mInNoti = null; 105 106 /** 107 * This inner class is used to describe some properties for one transfer. 108 */ 109 static class NotificationItem { 110 int id; // This first field _id in db; 111 112 int direction; // to indicate sending or receiving 113 114 int totalCurrent = 0; // current transfer bytes 115 116 int totalTotal = 0; // total bytes for current transfer 117 118 String description; // the text above progress bar 119 } 120 121 /** 122 * Constructor 123 * 124 * @param ctx The context to use to obtain access to the Notification 125 * Service 126 */ 127 BluetoothOppNotification(Context ctx) { 128 mContext = ctx; 129 mNotificationMgr = (NotificationManager)mContext 130 .getSystemService(Context.NOTIFICATION_SERVICE); 131 mNotifications = new HashMap<String, NotificationItem>(); 132 } 133 134 public void finishNotification() { 135 synchronized (BluetoothOppNotification.this) { 136 mFinised = true; 137 } 138 } 139 140 /** 141 * Update the notification ui. 142 */ 143 public void updateNotification() { 144 synchronized (BluetoothOppNotification.this) { 145 mPendingUpdate = true; 146 if (mUpdateNotificationThread == null) { 147 mUpdateNotificationThread = new NotificationUpdateThread(); 148 mUpdateNotificationThread.start(); 149 mFinised = false; 150 } 151 } 152 } 153 154 private class NotificationUpdateThread extends Thread { 155 156 public NotificationUpdateThread() { 157 super("Notification Update Thread"); 158 } 159 160 @Override 161 public void run() { 162 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 163 for (;;) { 164 synchronized (BluetoothOppNotification.this) { 165 if (mUpdateNotificationThread != this) { 166 throw new IllegalStateException( 167 "multiple UpdateThreads in BluetoothOppNotification"); 168 } 169 if (!mPendingUpdate && mFinised) { 170 mUpdateNotificationThread = null; 171 return; 172 } 173 mPendingUpdate = false; 174 } 175 updateActiveNotification(); 176 updateCompletedNotification(); 177 updateIncomingFileConfirmNotification(); 178 } 179 } 180 } 181 182 private void updateActiveNotification() { 183 // Active transfers 184 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 185 WHERE_RUNNING, null, BluetoothShare._ID); 186 if (cursor == null) { 187 return; 188 } 189 190 // If there is active transfers, then no need to update completed transfer 191 // notifications 192 if (cursor.getCount() > 0) { 193 mUpdateCompleteNotification = false; 194 } else { 195 mUpdateCompleteNotification = true; 196 } 197 if (V) Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification); 198 199 // Collate the notifications 200 final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP); 201 final int directionIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION); 202 final int idIndex = cursor.getColumnIndexOrThrow(BluetoothShare._ID); 203 final int totalBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES); 204 final int currentBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES); 205 final int dataIndex = cursor.getColumnIndexOrThrow(BluetoothShare._DATA); 206 final int filenameHintIndex = cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT); 207 208 mNotifications.clear(); 209 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 210 int timeStamp = cursor.getInt(timestampIndex); 211 int dir = cursor.getInt(directionIndex); 212 int id = cursor.getInt(idIndex); 213 int total = cursor.getInt(totalBytesIndex); 214 int current = cursor.getInt(currentBytesIndex); 215 216 String fileName = cursor.getString(dataIndex); 217 if (fileName == null) { 218 fileName = cursor.getString(filenameHintIndex); 219 } 220 if (fileName == null) { 221 fileName = mContext.getString(R.string.unknown_file); 222 } 223 224 String batchID = Long.toString(timeStamp); 225 226 // sending objects in one batch has same timeStamp 227 if (mNotifications.containsKey(batchID)) { 228 // NOTE: currently no such case 229 // Batch sending case 230 } else { 231 NotificationItem item = new NotificationItem(); 232 item.id = id; 233 item.direction = dir; 234 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 235 item.description = mContext.getString(R.string.notification_sending, fileName); 236 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 237 item.description = mContext 238 .getString(R.string.notification_receiving, fileName); 239 } else { 240 if (V) Log.v(TAG, "mDirection ERROR!"); 241 } 242 item.totalCurrent = current; 243 item.totalTotal = total; 244 245 mNotifications.put(batchID, item); 246 247 if (V) Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent" 248 + item.totalCurrent + "; totalTotal=" + item.totalTotal); 249 } 250 } 251 cursor.close(); 252 253 // Add the notifications 254 for (NotificationItem item : mNotifications.values()) { 255 // Build the RemoteView object 256 RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME, 257 R.layout.status_bar_ongoing_event_progress_bar); 258 259 expandedView.setTextViewText(R.id.description, item.description); 260 261 expandedView.setProgressBar(R.id.progress_bar, item.totalTotal, item.totalCurrent, 262 item.totalTotal == -1); 263 264 expandedView.setTextViewText(R.id.progress_text, BluetoothOppUtility 265 .formatProgressText(item.totalTotal, item.totalCurrent)); 266 267 // Build the notification object 268 Notification n = new Notification(); 269 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 270 n.icon = android.R.drawable.stat_sys_upload; 271 expandedView.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_upload); 272 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 273 n.icon = android.R.drawable.stat_sys_download; 274 expandedView.setImageViewResource(R.id.appIcon, 275 android.R.drawable.stat_sys_download); 276 } else { 277 if (V) Log.v(TAG, "mDirection ERROR!"); 278 } 279 280 n.flags |= Notification.FLAG_ONGOING_EVENT; 281 n.contentView = expandedView; 282 283 Intent intent = new Intent(Constants.ACTION_LIST); 284 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 285 intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); 286 287 n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 288 mNotificationMgr.notify(item.id, n); 289 290 mActiveNotificationId = item.id; 291 } 292 } 293 294 private void updateCompletedNotification() { 295 String title; 296 String caption; 297 long timeStamp = 0; 298 int outboundSuccNumber = 0; 299 int outboundFailNumber = 0; 300 int outboundNum; 301 int inboundNum; 302 int inboundSuccNumber = 0; 303 int inboundFailNumber = 0; 304 Intent intent; 305 306 // If there is active transfer, no need to update complete transfer 307 // notification 308 if (!mUpdateCompleteNotification) { 309 if (V) Log.v(TAG, "No need to update complete notification"); 310 return; 311 } 312 313 // After merge complete notifications to 2 notifications, there is no 314 // chance to update the active notifications to complete notifications 315 // as before. So need cancel the active notification after the active 316 // transfer becomes complete. 317 if (mNotificationMgr != null && mActiveNotificationId != 0) { 318 mNotificationMgr.cancel(mActiveNotificationId); 319 if (V) Log.v(TAG, "ongoing transfer notification was removed"); 320 } 321 322 // Creating outbound notification 323 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 324 WHERE_COMPLETED_OUTBOUND, null, BluetoothShare.TIMESTAMP + " DESC"); 325 if (cursor == null) { 326 return; 327 } 328 329 final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP); 330 final int statusIndex = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS); 331 332 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 333 if (cursor.isFirst()) { 334 // Display the time for the latest transfer 335 timeStamp = cursor.getLong(timestampIndex); 336 } 337 int status = cursor.getInt(statusIndex); 338 339 if (BluetoothShare.isStatusError(status)) { 340 outboundFailNumber++; 341 } else { 342 outboundSuccNumber++; 343 } 344 } 345 if (V) Log.v(TAG, "outbound: succ-" + outboundSuccNumber + " fail-" + outboundFailNumber); 346 cursor.close(); 347 348 outboundNum = outboundSuccNumber + outboundFailNumber; 349 // create the outbound notification 350 if (outboundNum > 0) { 351 mOutNoti = new Notification(); 352 mOutNoti.icon = android.R.drawable.stat_sys_upload_done; 353 title = mContext.getString(R.string.outbound_noti_title); 354 caption = mContext.getString(R.string.noti_caption, outboundSuccNumber, 355 outboundFailNumber); 356 intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER); 357 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 358 mOutNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast( 359 mContext, 0, intent, 0)); 360 mOutNoti.when = timeStamp; 361 mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND, mOutNoti); 362 } else { 363 if (mNotificationMgr != null && mOutNoti != null) { 364 mNotificationMgr.cancel(NOTIFICATION_ID_OUTBOUND); 365 mOutNoti = null; 366 if (V) Log.v(TAG, "outbound notification was removed."); 367 } 368 } 369 370 // Creating inbound notification 371 cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 372 WHERE_COMPLETED_INBOUND, null, BluetoothShare.TIMESTAMP + " DESC"); 373 if (cursor == null) { 374 return; 375 } 376 377 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 378 if (cursor.isFirst()) { 379 // Display the time for the latest transfer 380 timeStamp = cursor.getLong(timestampIndex); 381 } 382 int status = cursor.getInt(statusIndex); 383 384 if (BluetoothShare.isStatusError(status)) { 385 inboundFailNumber++; 386 } else { 387 inboundSuccNumber++; 388 } 389 } 390 if (V) Log.v(TAG, "inbound: succ-" + inboundSuccNumber + " fail-" + inboundFailNumber); 391 cursor.close(); 392 393 inboundNum = inboundSuccNumber + inboundFailNumber; 394 // create the inbound notification 395 if (inboundNum > 0) { 396 mInNoti = new Notification(); 397 mInNoti.icon = android.R.drawable.stat_sys_download_done; 398 title = mContext.getString(R.string.inbound_noti_title); 399 caption = mContext.getString(R.string.noti_caption, inboundSuccNumber, 400 inboundFailNumber); 401 intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER); 402 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 403 mInNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast( 404 mContext, 0, intent, 0)); 405 mInNoti.when = timeStamp; 406 mNotificationMgr.notify(NOTIFICATION_ID_INBOUND, mInNoti); 407 } else { 408 if (mNotificationMgr != null && mInNoti != null) { 409 mNotificationMgr.cancel(NOTIFICATION_ID_INBOUND); 410 mInNoti = null; 411 if (V) Log.v(TAG, "inbound notification was removed."); 412 } 413 } 414 } 415 416 private void updateIncomingFileConfirmNotification() { 417 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 418 WHERE_CONFIRM_PENDING, null, BluetoothShare._ID); 419 420 if (cursor == null) { 421 return; 422 } 423 424 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 425 String title = mContext.getString(R.string.incoming_file_confirm_Notification_title); 426 String caption = mContext 427 .getString(R.string.incoming_file_confirm_Notification_caption); 428 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 429 long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 430 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 431 432 Notification n = new Notification(); 433 n.icon = R.drawable.bt_incomming_file_notification; 434 n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; 435 n.defaults = Notification.DEFAULT_SOUND; 436 n.tickerText = title; 437 Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); 438 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 439 intent.setData(contentUri); 440 441 n.when = timeStamp; 442 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, 443 intent, 0)); 444 445 intent = new Intent(Constants.ACTION_HIDE); 446 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 447 intent.setData(contentUri); 448 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 449 450 mNotificationMgr.notify(id, n); 451 } 452 cursor.close(); 453 } 454} 455