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