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.Notification.Action;
41import android.app.NotificationManager;
42import android.app.NotificationChannel;
43import android.app.PendingIntent;
44import android.content.Intent;
45import android.database.Cursor;
46import android.net.Uri;
47import android.text.format.Formatter;
48import android.util.Log;
49import android.os.Handler;
50import android.os.Message;
51import android.os.Process;
52
53import 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 */
60class 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     */
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     */
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
204        public 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 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
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