BluetoothOppNotification.java revision 6ee2d0e0405a074cf827e0c0ac4bfbffd1850cea
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 (this) {
118            mFinised = true;
119        }
120    }
121
122    /**
123     * Update the notification ui.
124     */
125    public void updateNotification() {
126        synchronized (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 (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