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