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