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