• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ContentResolver;
38 import android.content.Context;
39 import android.app.Notification;
40 import android.app.Notification.Action;
41 import android.app.NotificationManager;
42 import android.app.NotificationChannel;
43 import android.app.PendingIntent;
44 import android.content.Intent;
45 import android.database.Cursor;
46 import android.net.Uri;
47 import android.text.format.Formatter;
48 import android.util.Log;
49 import android.os.Handler;
50 import android.os.Message;
51 import android.os.Process;
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 = "(" + BluetoothShare.VISIBILITY + " IS NULL OR "
67             + BluetoothShare.VISIBILITY + " == '" + 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 = BluetoothShare.STATUS + " >= '200' AND " + visible +
82             " AND " + not_through_handover; // Don't show handover-initiated transfers
83 
84     private static final String WHERE_COMPLETED_OUTBOUND = WHERE_COMPLETED + " AND " + "("
85             + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND + ")";
86 
87     private static final String WHERE_COMPLETED_INBOUND = WHERE_COMPLETED + " AND " + "("
88             + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND + ")";
89 
90     static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '"
91             + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible;
92 
93     public NotificationManager mNotificationMgr;
94 
95     private NotificationChannel mNotificationChannel;
96     private static final String OPP_NOTIFICATION_CHANNEL = "opp_notification_channel";
97 
98     private Context mContext;
99 
100     private HashMap<String, NotificationItem> mNotifications;
101 
102     private NotificationUpdateThread mUpdateNotificationThread;
103 
104     private int mPendingUpdate = 0;
105 
106     public static final int NOTIFICATION_ID_PROGRESS = -1000004;
107 
108     private static final int NOTIFICATION_ID_OUTBOUND_COMPLETE = -1000005;
109 
110     private static final int NOTIFICATION_ID_INBOUND_COMPLETE = -1000006;
111 
112     private boolean mUpdateCompleteNotification = true;
113 
114     private ContentResolver mContentResolver = null;
115     /**
116      * This inner class is used to describe some properties for one transfer.
117      */
118     static class NotificationItem {
119         int id; // This first field _id in db;
120 
121         int direction; // to indicate sending or receiving
122 
123         long totalCurrent = 0; // current transfer bytes
124 
125         long totalTotal = 0; // total bytes for current transfer
126 
127         long timeStamp = 0; // Database time stamp. Used for sorting ongoing transfers.
128 
129         String description; // the text above progress bar
130 
131         boolean handoverInitiated = false; // transfer initiated by connection handover (eg NFC)
132 
133         String destination; // destination associated with this transfer
134     }
135 
136     /**
137      * Constructor
138      *
139      * @param ctx The context to use to obtain access to the Notification
140      *            Service
141      */
BluetoothOppNotification(Context ctx)142     BluetoothOppNotification(Context ctx) {
143         mContext = ctx;
144         mNotificationMgr = (NotificationManager)mContext
145                 .getSystemService(Context.NOTIFICATION_SERVICE);
146         mNotificationChannel = new NotificationChannel(OPP_NOTIFICATION_CHANNEL,
147                 mContext.getString(R.string.opp_notification_group),
148                 NotificationManager.IMPORTANCE_HIGH);
149 
150         mNotificationMgr.createNotificationChannel(mNotificationChannel);
151         mNotifications = new HashMap<String, NotificationItem>();
152         // Get Content Resolver object one time
153         mContentResolver = mContext.getContentResolver();
154     }
155 
156     /**
157      * Update the notification ui.
158      */
updateNotification()159     public void updateNotification() {
160         synchronized (BluetoothOppNotification.this) {
161             mPendingUpdate++;
162             if (mPendingUpdate > 1) {
163                 if (V) Log.v(TAG, "update too frequent, put in queue");
164                 return;
165             }
166             if (!mHandler.hasMessages(NOTIFY)) {
167                 if (V) Log.v(TAG, "send message");
168                 mHandler.sendMessage(mHandler.obtainMessage(NOTIFY));
169             }
170         }
171     }
172 
173     private static final int NOTIFY = 0;
174     // Use 1 second timer to limit notification frequency.
175     // 1. On the first notification, create the update thread.
176     //    Buffer other updates.
177     // 2. Update thread will clear mPendingUpdate.
178     // 3. Handler sends a delayed message to self
179     // 4. Handler checks if there are any more updates after 1 second.
180     // 5. If there is an update, update it else stop.
181     private Handler mHandler = new Handler() {
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 
NotificationUpdateThread()204         public NotificationUpdateThread() {
205             super("Notification Update Thread");
206         }
207 
208         @Override
run()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 
updateActiveNotification()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 
updateCompletedNotification()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 unsuccess_caption = 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                     unsuccess_caption);
411             Intent content_intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER)
412                     .setClassName(Constants.THIS_PACKAGE_NAME,
413                             BluetoothOppReceiver.class.getName());
414             Intent delete_intent = 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, content_intent, 0))
428                             .setDeleteIntent(
429                                     PendingIntent.getBroadcast(mContext, 0, delete_intent, 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 unsuccess_caption = 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                     unsuccess_caption);
471             Intent content_intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER)
472                     .setClassName(Constants.THIS_PACKAGE_NAME,
473                             BluetoothOppReceiver.class.getName());
474             Intent delete_intent = 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, content_intent, 0))
488                             .setDeleteIntent(
489                                     PendingIntent.getBroadcast(mContext, 0, delete_intent, 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 
updateIncomingFileConfirmNotification()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