BluetoothOppNotification.java revision c6f1bacf1d4c39d7efd5dc98004d936c18ed51f1
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.Context; 38import android.app.Notification; 39import android.app.NotificationManager; 40import android.app.PendingIntent; 41import android.content.Intent; 42import android.database.Cursor; 43import android.net.Uri; 44import android.util.Log; 45import android.widget.RemoteViews; 46import android.os.Process; 47import java.util.HashMap; 48 49/** 50 * This class handles the updating of the Notification Manager for the cases 51 * where there is an ongoing transfer, incoming transfer need confirm and 52 * complete (successful or failed) transfer. 53 */ 54class BluetoothOppNotification { 55 private static final String TAG = "BluetoothOppNotification"; 56 private static final boolean D = Constants.DEBUG; 57 private static final boolean V = Constants.VERBOSE; 58 59 static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")"; 60 61 static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR " 62 + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")"; 63 64 static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '" 65 + BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR " 66 + BluetoothShare.USER_CONFIRMATION + " == '" 67 + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "'" + ")"; 68 69 static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm; 70 71 static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible; 72 73 static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '" 74 + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible; 75 76 public NotificationManager mNotificationMgr; 77 78 private Context mContext; 79 80 private HashMap<String, NotificationItem> mNotifications; 81 82 private NotificationUpdateThread mUpdateNotificationThread; 83 84 private boolean mPendingUpdate = false; 85 86 private boolean mFinised = false; 87 88 /** 89 * This inner class is used to describe some properties for one transfer. 90 */ 91 static class NotificationItem { 92 int id; // This first field _id in db; 93 94 int direction; // to indicate sending or receiving 95 96 int totalCurrent = 0; // current transfer bytes 97 98 int totalTotal = 0; // total bytes for current transfer 99 100 String description; // the text above progress bar 101 } 102 103 /** 104 * Constructor 105 * 106 * @param ctx The context to use to obtain access to the Notification 107 * Service 108 */ 109 BluetoothOppNotification(Context ctx) { 110 mContext = ctx; 111 mNotificationMgr = (NotificationManager)mContext 112 .getSystemService(Context.NOTIFICATION_SERVICE); 113 mNotifications = new HashMap<String, NotificationItem>(); 114 } 115 116 public void finishNotification() { 117 synchronized (BluetoothOppNotification.this) { 118 mFinised = true; 119 } 120 } 121 122 /** 123 * Update the notification ui. 124 */ 125 public void updateNotification() { 126 synchronized (BluetoothOppNotification.this) { 127 mPendingUpdate = true; 128 if (mUpdateNotificationThread == null) { 129 mUpdateNotificationThread = new NotificationUpdateThread(); 130 mUpdateNotificationThread.start(); 131 mFinised = false; 132 } 133 } 134 } 135 136 private class NotificationUpdateThread extends Thread { 137 138 public NotificationUpdateThread() { 139 super("Notification Update Thread"); 140 } 141 142 @Override 143 public void run() { 144 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 145 for (;;) { 146 synchronized (BluetoothOppNotification.this) { 147 if (mUpdateNotificationThread != this) { 148 throw new IllegalStateException( 149 "multiple UpdateThreads in BluetoothOppNotification"); 150 } 151 if (!mPendingUpdate && mFinised) { 152 mUpdateNotificationThread = null; 153 return; 154 } 155 mPendingUpdate = false; 156 } 157 updateActiveNotification(); 158 updateCompletedNotification(); 159 updateIncomingFileConfirmNotification(); 160 } 161 } 162 } 163 164 private void updateActiveNotification() { 165 // Active transfers 166 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 167 WHERE_RUNNING, null, BluetoothShare._ID); 168 if (cursor == null) { 169 return; 170 } 171 172 // Collate the notifications 173 mNotifications.clear(); 174 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 175 int timeStamp = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 176 int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)); 177 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 178 int total = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)); 179 int current = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)); 180 181 String fileName = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)); 182 if (fileName == null) { 183 fileName = cursor.getString(cursor 184 .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 185 } 186 if (fileName == null) { 187 fileName = mContext.getString(R.string.unknown_file); 188 } 189 190 String batchID = Long.toString(timeStamp); 191 192 // sending objects in one batch has same timeStamp 193 if (mNotifications.containsKey(batchID)) { 194 // NOTE: currently no such case 195 // Batch sending case 196 } else { 197 NotificationItem item = new NotificationItem(); 198 item.id = id; 199 item.direction = dir; 200 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 201 item.description = mContext.getString(R.string.notification_sending, fileName); 202 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 203 item.description = mContext 204 .getString(R.string.notification_receiving, fileName); 205 } else { 206 if (V) Log.v(TAG, "mDirection ERROR!"); 207 } 208 item.totalCurrent = current; 209 item.totalTotal = total; 210 211 mNotifications.put(batchID, item); 212 213 if (V) Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent" 214 + item.totalCurrent + "; totalTotal=" + item.totalTotal); 215 } 216 } 217 cursor.close(); 218 219 // Add the notifications 220 for (NotificationItem item : mNotifications.values()) { 221 // Build the RemoteView object 222 RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME, 223 R.layout.status_bar_ongoing_event_progress_bar); 224 225 expandedView.setTextViewText(R.id.description, item.description); 226 227 expandedView.setProgressBar(R.id.progress_bar, item.totalTotal, item.totalCurrent, 228 item.totalTotal == -1); 229 230 expandedView.setTextViewText(R.id.progress_text, BluetoothOppUtility 231 .formatProgressText(item.totalTotal, item.totalCurrent)); 232 233 // Build the notification object 234 Notification n = new Notification(); 235 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) { 236 n.icon = android.R.drawable.stat_sys_upload; 237 expandedView.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_upload); 238 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) { 239 n.icon = android.R.drawable.stat_sys_download; 240 expandedView.setImageViewResource(R.id.appIcon, 241 android.R.drawable.stat_sys_download); 242 } else { 243 if (V) Log.v(TAG, "mDirection ERROR!"); 244 } 245 246 n.flags |= Notification.FLAG_ONGOING_EVENT; 247 n.contentView = expandedView; 248 249 Intent intent = new Intent(Constants.ACTION_LIST); 250 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 251 intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); 252 253 n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 254 mNotificationMgr.notify(item.id, n); 255 } 256 } 257 258 private void updateCompletedNotification() { 259 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 260 WHERE_COMPLETED, null, BluetoothShare._ID); 261 if (cursor == null) { 262 return; 263 } 264 265 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 266 // Add the notifications 267 long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 268 int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)); 269 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 270 int status = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)); 271 272 String fileName = cursor.getString(cursor 273 .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 274 if (fileName == null) { 275 fileName = mContext.getString(R.string.unknown_file); 276 } 277 278 String title; 279 String caption; 280 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 281 282 Notification n = new Notification(); 283 if (BluetoothShare.isStatusError(status)) { 284 if (dir == BluetoothShare.DIRECTION_OUTBOUND) { 285 title = mContext.getString(R.string.notification_sent_fail, fileName); 286 } else { 287 title = mContext.getString(R.string.notification_received_fail, fileName); 288 } 289 caption = mContext.getString(R.string.download_fail_line3, BluetoothOppUtility 290 .getStatusDescription(mContext, status)); 291 n.icon = android.R.drawable.stat_notify_error; 292 } else { 293 if (dir == BluetoothShare.DIRECTION_OUTBOUND) { 294 title = mContext.getString(R.string.notification_sent, fileName); 295 n.icon = android.R.drawable.stat_sys_upload_done; 296 } else { 297 title = mContext.getString(R.string.notification_received, fileName); 298 n.icon = android.R.drawable.stat_sys_download_done; 299 } 300 caption = mContext.getString(R.string.notification_sent_complete); 301 } 302 Intent intent = new Intent(Constants.ACTION_OPEN); 303 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 304 intent.setData(contentUri); 305 306 n.when = timeStamp; 307 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, 308 intent, 0)); 309 310 intent = new Intent(Constants.ACTION_HIDE); 311 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 312 intent.setData(contentUri); 313 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 314 315 mNotificationMgr.notify(id, n); 316 } 317 cursor.close(); 318 } 319 320 private void updateIncomingFileConfirmNotification() { 321 Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, 322 WHERE_CONFIRM_PENDING, null, BluetoothShare._ID); 323 324 if (cursor == null) { 325 return; 326 } 327 328 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 329 String title = mContext.getString(R.string.incoming_file_confirm_Notification_title); 330 String caption = mContext 331 .getString(R.string.incoming_file_confirm_Notification_caption); 332 int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 333 long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 334 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 335 336 Notification n = new Notification(); 337 n.icon = R.drawable.bt_incomming_file_notification; 338 n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; 339 n.defaults = Notification.DEFAULT_SOUND; 340 n.tickerText = title; 341 Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); 342 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 343 intent.setData(contentUri); 344 345 n.when = timeStamp; 346 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, 347 intent, 0)); 348 349 intent = new Intent(Constants.ACTION_HIDE); 350 intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 351 intent.setData(contentUri); 352 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 353 354 mNotificationMgr.notify(id, n); 355 } 356 cursor.close(); 357 } 358} 359