Home | History | Annotate | Download | only in opp
      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