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