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 33 package com.android.bluetooth.opp; 34 35 import com.android.bluetooth.R; 36 37 import android.content.Context; 38 import android.app.Notification; 39 import android.app.NotificationManager; 40 import android.app.PendingIntent; 41 import android.content.Intent; 42 import android.database.Cursor; 43 import android.net.Uri; 44 import android.text.format.Formatter; 45 import android.util.Log; 46 import android.os.Handler; 47 import android.os.Message; 48 import android.os.Process; 49 50 import java.util.HashMap; 51 52 /** 53 * This class handles the updating of the Notification Manager for the cases 54 * where there is an ongoing transfer, incoming transfer need confirm and 55 * complete (successful or failed) transfer. 56 */ 57 class BluetoothOppNotification { 58 private static final String TAG = "BluetoothOppNotification"; 59 private static final boolean V = Constants.VERBOSE; 60 61 static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")"; 62 63 static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR " 64 + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")"; 65 66 static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '" 67 + BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR " 68 + BluetoothShare.USER_CONFIRMATION + " == '" 69 + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "' OR " 70 + BluetoothShare.USER_CONFIRMATION + " == '" 71 + BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED + "'" + ")"; 72 73 static final String not_through_handover = "(" + BluetoothShare.USER_CONFIRMATION + " != '" 74 + BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED + "'" + ")"; 75 76 static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm; 77 78 static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible + 79 " AND " + not_through_handover; // Don't show handover-initiated transfers 80 81 private static final String WHERE_COMPLETED_OUTBOUND = WHERE_COMPLETED + " AND " + "(" 82 + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND + ")"; 83 84 private static final String WHERE_COMPLETED_INBOUND = WHERE_COMPLETED + " AND " + "(" 85 + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND + ")"; 86 87 static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '" 88 + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible; 89 90 public NotificationManager mNotificationMgr; 91 92 private Context mContext; 93 94 private HashMap<String, NotificationItem> mNotifications; 95 96 private NotificationUpdateThread mUpdateNotificationThread; 97 98 private int mPendingUpdate = 0; 99 100 private static final int NOTIFICATION_ID_OUTBOUND = -1000005; 101 102 private static final int NOTIFICATION_ID_INBOUND = -1000006; 103 104 private boolean mUpdateCompleteNotification = true; 105 106 private int mActiveNotificationId = 0; 107 108 /** 109 * This inner class is used to describe some properties for one transfer. 110 */ 111 static class NotificationItem { 112 int id; // This first field _id in db; 113 114 int direction; // to indicate sending or receiving 115 116 long totalCurrent = 0; // current transfer bytes 117 118 long totalTotal = 0; // total bytes for current transfer 119 120 long timeStamp = 0; // Database time stamp. Used for sorting ongoing transfers. 121 122 String description; // the text above progress bar 123 124 boolean handoverInitiated = false; // transfer initiated by connection handover (eg NFC) 125 126 String destination; // destination associated with this transfer 127 } 128 129 /** 130 * Constructor 131 * 132 * @param ctx The context to use to obtain access to the Notification 133 * Service 134 */ 135 BluetoothOppNotification(Context ctx) { 136 mContext = ctx; 137 mNotificationMgr = (NotificationManager)mContext 138 .getSystemService(Context.NOTIFICATION_SERVICE); 139 mNotifications = new HashMap<String, NotificationItem>(); 140 } 141 142 /** 143 * Update the notification ui. 144 */ 145 public void updateNotification() { 146 synchronized (BluetoothOppNotification.this) { 147 mPendingUpdate++; 148 if (mPendingUpdate > 1) { 149 if (V) Log.v(TAG, "update too frequent, put in queue"); 150 return; 151 } 152 if (!mHandler.hasMessages(NOTIFY)) { 153 if (V) Log.v(TAG, "send message"); 154 mHandler.sendMessage(mHandler.obtainMessage(NOTIFY)); 155 } 156 } 157 } 158 159 private static final int NOTIFY = 0; 160 // Use 1 second timer to limit notification frequency. 161 // 1. On the first notification, create the update thread. 162 // Buffer other updates. 163 // 2. Update thread will clear mPendingUpdate. 164 // 3. Handler sends a delayed message to self 165 // 4. Handler checks if there are any more updates after 1 second. 166 // 5. If there is an update, update it else stop. 167 private Handler mHandler = new Handler() { 168 public void handleMessage(Message msg) { 169 switch (msg.what) { 170 case NOTIFY: 171 synchronized (BluetoothOppNotification.this) { 172 if (mPendingUpdate > 0 && mUpdateNotificationThread == null) { 173 if (V) Log.v(TAG, "new notify threadi!"); 174 mUpdateNotificationThread = new NotificationUpdateThread(); 175 mUpdateNotificationThread.start(); 176 if (V) Log.v(TAG, "send delay message"); 177 mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000); 178 } else if (mPendingUpdate > 0) { 179 if (V) Log.v(TAG, "previous thread is not finished yet"); 180 mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000); 181 } 182 break; 183 } 184 } 185 } 186 }; 187 188 private class NotificationUpdateThread extends Thread { 189 190 public NotificationUpdateThread() { 191 super("Notification Update Thread"); 192 } 193 194 @Override 195 public void run() { 196 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 197 synchronized (BluetoothOppNotification.this) { 198 if (mUpdateNotificationThread != this) { 199 throw new IllegalStateException( 200 "multiple UpdateThreads in BluetoothOppNotification"); 201 } 202 mPendingUpdate = 0; 203 } 204 updateActiveNotification(); 205 updateCompletedNotification(); 206 updateIncomingFileConfirmNotification(); 207 synchronized (BluetoothOppNotification.this) { 208 mUpdateNotificationThread = null; 209 } 210 } 211 } 212 213 private void updateActiveNotification() { 214 // Active transfers 215 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 216 WHERE_RUNNING, null, BluetoothShare._ID); 217 if (cursor == null) { 218 return; 219 } 220 221 // If there is active transfers, then no need to update completed transfer 222 // notifications 223 if (cursor.getCount() > 0) { 224 mUpdateCompleteNotification = false; 225 } else { 226 mUpdateCompleteNotification = true; 227 } 228 if (V) Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification); 229 230 // Collate the notifications 231 final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP); 232 final int directionIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION); 233 final int idIndex = cursor.getColumnIndexOrThrow(BluetoothShare._ID); 234 final int totalBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES); 235 final int currentBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES); 236 final int dataIndex = cursor.getColumnIndexOrThrow(BluetoothShare._DATA); 237 final int filenameHintIndex = cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT); 238 final int confirmIndex = cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION); 239 final int destinationIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION); 240 241 mNotifications.clear(); 242 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 243 long timeStamp = cursor.getLong(timestampIndex); 244 int dir = cursor.getInt(directionIndex); 245 int id = cursor.getInt(idIndex); 246 long total = cursor.getLong(totalBytesIndex); 247 long current = cursor.getLong(currentBytesIndex); 248 int confirmation = cursor.getInt(confirmIndex); 249 250 String destination = cursor.getString(destinationIndex); 251 String fileName = cursor.getString(dataIndex); 252 if (fileName == null) { 253 fileName = cursor.getString(filenameHintIndex); 254 } 255 if (fileName == null) { 256 fileName = mContext.getString(R.string.unknown_file); 257 } 258 259 String batchID = Long.toString(timeStamp); 260 261 // sending objects in one batch has same timeStamp 262 if (mNotifications.containsKey(batchID)) { 263 // NOTE: currently no such case 264 // Batch sending case 265 } else { 266 NotificationItem item = new NotificationItem(); 267 item.timeStamp = timeStamp; 268 item.id = id; 269 item.direction = dir; 270 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 271 item.description = mContext.getString(R.string.notification_sending, fileName); 272 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 273 item.description = mContext 274 .getString(R.string.notification_receiving, fileName); 275 } else { 276 if (V) Log.v(TAG, "mDirection ERROR!"); 277 } 278 item.totalCurrent = current; 279 item.totalTotal = total; 280 item.handoverInitiated = 281 confirmation == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED; 282 item.destination = destination; 283 mNotifications.put(batchID, item); 284 285 if (V) Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent" 286 + item.totalCurrent + "; totalTotal=" + item.totalTotal); 287 } 288 } 289 cursor.close(); 290 291 // Add the notifications 292 for (NotificationItem item : mNotifications.values()) { 293 if (item.handoverInitiated) { 294 float progress = 0; 295 if (item.totalTotal == -1) { 296 progress = -1; 297 } else { 298 progress = (float)item.totalCurrent / item.totalTotal; 299 } 300 301 // Let NFC service deal with notifications for this transfer 302 Intent intent = new Intent(Constants.ACTION_BT_OPP_TRANSFER_PROGRESS); 303 if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 304 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_DIRECTION, 305 Constants.DIRECTION_BLUETOOTH_INCOMING); 306 } else { 307 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_DIRECTION, 308 Constants.DIRECTION_BLUETOOTH_OUTGOING); 309 } 310 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_ID, item.id); 311 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_PROGRESS, progress); 312 intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, item.destination); 313 mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION); 314 continue; 315 } 316 // Build the notification object 317 // TODO: split description into two rows with filename in second row 318 Notification.Builder b = new Notification.Builder(mContext); 319 b.setColor(mContext.getResources().getColor( 320 com.android.internal.R.color.system_notification_accent_color)); 321 b.setContentTitle(item.description); 322 b.setContentInfo( 323 BluetoothOppUtility.formatProgressText(item.totalTotal, item.totalCurrent)); 324 if (item.totalTotal != 0) { 325 if (V) Log.v(TAG, "mCurrentBytes: " + item.totalCurrent + 326 " mTotalBytes: " + item.totalTotal + " (" + 327 (int)((item.totalCurrent * 100) / item.totalTotal) + " %)"); 328 b.setProgress(100, (int)((item.totalCurrent * 100) / item.totalTotal), 329 item.totalTotal == -1); 330 } else { 331 b.setProgress(100, 100, item.totalTotal == -1); 332 } 333 b.setWhen(item.timeStamp); 334 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 335 b.setSmallIcon(android.R.drawable.stat_sys_upload); 336 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 337 b.setSmallIcon(android.R.drawable.stat_sys_download); 338 } else { 339 if (V) Log.v(TAG, "mDirection ERROR!"); 340 } 341 b.setOngoing(true); 342 343 Intent intent = new Intent(Constants.ACTION_LIST); 344 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 345 intent.setDataAndNormalize(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); 346 347 b.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 0)); 348 mNotificationMgr.notify(item.id, b.getNotification()); 349 350 mActiveNotificationId = item.id; 351 } 352 } 353 354 private void updateCompletedNotification() { 355 String title; 356 String unsuccess_caption; 357 String caption; 358 long timeStamp = 0; 359 int outboundSuccNumber = 0; 360 int outboundFailNumber = 0; 361 int outboundNum; 362 int inboundNum; 363 int inboundSuccNumber = 0; 364 int inboundFailNumber = 0; 365 Intent intent; 366 367 // If there is active transfer, no need to update complete transfer 368 // notification 369 if (!mUpdateCompleteNotification) { 370 if (V) Log.v(TAG, "No need to update complete notification"); 371 return; 372 } 373 374 // After merge complete notifications to 2 notifications, there is no 375 // chance to update the active notifications to complete notifications 376 // as before. So need cancel the active notification after the active 377 // transfer becomes complete. 378 if (mNotificationMgr != null && mActiveNotificationId != 0) { 379 mNotificationMgr.cancel(mActiveNotificationId); 380 if (V) Log.v(TAG, "ongoing transfer notification was removed"); 381 } 382 383 // Creating outbound notification 384 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 385 WHERE_COMPLETED_OUTBOUND, null, BluetoothShare.TIMESTAMP + " DESC"); 386 if (cursor == null) { 387 return; 388 } 389 390 final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP); 391 final int statusIndex = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS); 392 393 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 394 if (cursor.isFirst()) { 395 // Display the time for the latest transfer 396 timeStamp = cursor.getLong(timestampIndex); 397 } 398 int status = cursor.getInt(statusIndex); 399 400 if (BluetoothShare.isStatusError(status)) { 401 outboundFailNumber++; 402 } else { 403 outboundSuccNumber++; 404 } 405 } 406 if (V) Log.v(TAG, "outbound: succ-" + outboundSuccNumber + " fail-" + outboundFailNumber); 407 cursor.close(); 408 409 outboundNum = outboundSuccNumber + outboundFailNumber; 410 // create the outbound notification 411 if (outboundNum > 0) { 412 Notification outNoti = new Notification(); 413 outNoti.icon = android.R.drawable.stat_sys_upload_done; 414 title = mContext.getString(R.string.outbound_noti_title); 415 unsuccess_caption = mContext.getResources().getQuantityString( 416 R.plurals.noti_caption_unsuccessful, outboundFailNumber, outboundFailNumber); 417 caption = mContext.getResources().getQuantityString( 418 R.plurals.noti_caption_success, outboundSuccNumber, outboundSuccNumber, 419 unsuccess_caption); 420 intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER); 421 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 422 outNoti.color = mContext.getResources().getColor( 423 com.android.internal.R.color.system_notification_accent_color); 424 outNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast( 425 mContext, 0, intent, 0)); 426 intent = new Intent(Constants.ACTION_COMPLETE_HIDE); 427 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 428 outNoti.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 429 outNoti.when = timeStamp; 430 mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND, outNoti); 431 } else { 432 if (mNotificationMgr != null) { 433 mNotificationMgr.cancel(NOTIFICATION_ID_OUTBOUND); 434 if (V) Log.v(TAG, "outbound notification was removed."); 435 } 436 } 437 438 // Creating inbound notification 439 cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 440 WHERE_COMPLETED_INBOUND, null, BluetoothShare.TIMESTAMP + " DESC"); 441 if (cursor == null) { 442 return; 443 } 444 445 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 446 if (cursor.isFirst()) { 447 // Display the time for the latest transfer 448 timeStamp = cursor.getLong(timestampIndex); 449 } 450 int status = cursor.getInt(statusIndex); 451 452 if (BluetoothShare.isStatusError(status)) { 453 inboundFailNumber++; 454 } else { 455 inboundSuccNumber++; 456 } 457 } 458 if (V) Log.v(TAG, "inbound: succ-" + inboundSuccNumber + " fail-" + inboundFailNumber); 459 cursor.close(); 460 461 inboundNum = inboundSuccNumber + inboundFailNumber; 462 // create the inbound notification 463 if (inboundNum > 0) { 464 Notification inNoti = new Notification(); 465 inNoti.icon = android.R.drawable.stat_sys_download_done; 466 title = mContext.getString(R.string.inbound_noti_title); 467 unsuccess_caption = mContext.getResources().getQuantityString( 468 R.plurals.noti_caption_unsuccessful, inboundFailNumber, inboundFailNumber); 469 caption = mContext.getResources().getQuantityString( 470 R.plurals.noti_caption_success, inboundSuccNumber, inboundSuccNumber, 471 unsuccess_caption); 472 intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER); 473 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 474 inNoti.color = mContext.getResources().getColor( 475 com.android.internal.R.color.system_notification_accent_color); 476 inNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast( 477 mContext, 0, intent, 0)); 478 intent = new Intent(Constants.ACTION_COMPLETE_HIDE); 479 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 480 inNoti.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 481 inNoti.when = timeStamp; 482 mNotificationMgr.notify(NOTIFICATION_ID_INBOUND, inNoti); 483 } else { 484 if (mNotificationMgr != null) { 485 mNotificationMgr.cancel(NOTIFICATION_ID_INBOUND); 486 if (V) Log.v(TAG, "inbound notification was removed."); 487 } 488 } 489 } 490 491 private void updateIncomingFileConfirmNotification() { 492 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 493 WHERE_CONFIRM_PENDING, null, BluetoothShare._ID); 494 495 if (cursor == null) { 496 return; 497 } 498 499 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 500 BluetoothOppTransferInfo info = new BluetoothOppTransferInfo(); 501 BluetoothOppUtility.fillRecord(mContext, cursor, info); 502 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID); 503 Intent baseIntent = new Intent().setDataAndNormalize(contentUri) 504 .setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 505 506 Notification n = new Notification.Builder(mContext) 507 .setOnlyAlertOnce(true) 508 .setOngoing(true) 509 .setVibrate(new long[] { 200 }) 510 .setWhen(info.mTimeStamp) 511 .setDefaults(Notification.DEFAULT_SOUND) 512 .setPriority(Notification.PRIORITY_HIGH) 513 .addAction(R.drawable.ic_decline, 514 mContext.getText(R.string.incoming_file_confirm_cancel), 515 PendingIntent.getBroadcast(mContext, 0, 516 new Intent(baseIntent).setAction(Constants.ACTION_DECLINE), 0)) 517 .addAction(R.drawable.ic_accept, 518 mContext.getText(R.string.incoming_file_confirm_ok), 519 PendingIntent.getBroadcast(mContext, 0, 520 new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0)) 521 .setContentIntent(PendingIntent.getBroadcast(mContext, 0, 522 new Intent(baseIntent).setAction(Constants.ACTION_INCOMING_FILE_CONFIRM), 0)) 523 .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, 524 new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0)) 525 .setColor(mContext.getResources().getColor( 526 com.android.internal.R.color.system_notification_accent_color)) 527 .setContentTitle(mContext.getText(R.string.incoming_file_confirm_Notification_title)) 528 .setContentText(info.mFileName) 529 .setStyle(new Notification.BigTextStyle().bigText(mContext.getString( 530 R.string.incoming_file_confirm_Notification_content, 531 info.mDeviceName, info.mFileName))) 532 .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes)) 533 .setSmallIcon(R.drawable.bt_incomming_file_notification) 534 .build(); 535 mNotificationMgr.notify(info.mID, n); 536 } 537 cursor.close(); 538 } 539 } 540