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