blob: e72ef3e5d11727e5ac9560ce51df8030512a97b6 [file] [log] [blame]
/*
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Motorola, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.bluetooth.opp;
import com.android.bluetooth.R;
import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import android.widget.RemoteViews;
import android.os.Process;
import java.util.HashMap;
/**
* This class handles the updating of the Notification Manager for the cases
* where there is an ongoing transfer, incoming transfer need confirm and
* complete (successful or failed) transfer.
*/
class BluetoothOppNotification {
private static final String TAG = "BluetoothOppNotification";
private static final boolean D = Constants.DEBUG;
private static final boolean V = Constants.VERBOSE;
static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")";
static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR "
+ BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")";
static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '"
+ BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR "
+ BluetoothShare.USER_CONFIRMATION + " == '"
+ BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "'" + ")";
static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm;
static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible;
private static final String WHERE_COMPLETED_OUTBOUND = WHERE_COMPLETED + " AND " + "("
+ BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND + ")";
private static final String WHERE_COMPLETED_INBOUND = WHERE_COMPLETED + " AND " + "("
+ BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND + ")";
static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '"
+ BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible;
public NotificationManager mNotificationMgr;
private Context mContext;
private HashMap<String, NotificationItem> mNotifications;
private NotificationUpdateThread mUpdateNotificationThread;
private boolean mPendingUpdate = false;
private boolean mFinised = false;
private static final int NOTIFICATION_ID_OUTBOUND = -1000005;
private static final int NOTIFICATION_ID_INBOUND = -1000006;
private boolean mUpdateCompleteNotification = true;
private int mActiveNotificationId = 0;
private Notification mOutNoti = null;
private Notification mInNoti = null;
/**
* This inner class is used to describe some properties for one transfer.
*/
static class NotificationItem {
int id; // This first field _id in db;
int direction; // to indicate sending or receiving
int totalCurrent = 0; // current transfer bytes
int totalTotal = 0; // total bytes for current transfer
String description; // the text above progress bar
}
/**
* Constructor
*
* @param ctx The context to use to obtain access to the Notification
* Service
*/
BluetoothOppNotification(Context ctx) {
mContext = ctx;
mNotificationMgr = (NotificationManager)mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotifications = new HashMap<String, NotificationItem>();
}
public void finishNotification() {
synchronized (BluetoothOppNotification.this) {
mFinised = true;
}
}
/**
* Update the notification ui.
*/
public void updateNotification() {
synchronized (BluetoothOppNotification.this) {
mPendingUpdate = true;
if (mUpdateNotificationThread == null) {
mUpdateNotificationThread = new NotificationUpdateThread();
mUpdateNotificationThread.start();
mFinised = false;
}
}
}
private class NotificationUpdateThread extends Thread {
public NotificationUpdateThread() {
super("Notification Update Thread");
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
for (;;) {
synchronized (BluetoothOppNotification.this) {
if (mUpdateNotificationThread != this) {
throw new IllegalStateException(
"multiple UpdateThreads in BluetoothOppNotification");
}
if (!mPendingUpdate && mFinised) {
mUpdateNotificationThread = null;
return;
}
mPendingUpdate = false;
}
updateActiveNotification();
updateCompletedNotification();
updateIncomingFileConfirmNotification();
}
}
}
private void updateActiveNotification() {
// Active transfers
Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,
WHERE_RUNNING, null, BluetoothShare._ID);
if (cursor == null) {
return;
}
// If there is active transfers, then no need to update completed transfer
// notifications
if (cursor.getCount() > 0) {
mUpdateCompleteNotification = false;
} else {
mUpdateCompleteNotification = true;
}
if (V) Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification);
// Collate the notifications
final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
final int directionIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION);
final int idIndex = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
final int totalBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES);
final int currentBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES);
final int dataIndex = cursor.getColumnIndexOrThrow(BluetoothShare._DATA);
final int filenameHintIndex = cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT);
mNotifications.clear();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
int timeStamp = cursor.getInt(timestampIndex);
int dir = cursor.getInt(directionIndex);
int id = cursor.getInt(idIndex);
int total = cursor.getInt(totalBytesIndex);
int current = cursor.getInt(currentBytesIndex);
String fileName = cursor.getString(dataIndex);
if (fileName == null) {
fileName = cursor.getString(filenameHintIndex);
}
if (fileName == null) {
fileName = mContext.getString(R.string.unknown_file);
}
String batchID = Long.toString(timeStamp);
// sending objects in one batch has same timeStamp
if (mNotifications.containsKey(batchID)) {
// NOTE: currently no such case
// Batch sending case
} else {
NotificationItem item = new NotificationItem();
item.id = id;
item.direction = dir;
if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) {
item.description = mContext.getString(R.string.notification_sending, fileName);
} else if (item.direction == BluetoothShare.DIRECTION_INBOUND) {
item.description = mContext
.getString(R.string.notification_receiving, fileName);
} else {
if (V) Log.v(TAG, "mDirection ERROR!");
}
item.totalCurrent = current;
item.totalTotal = total;
mNotifications.put(batchID, item);
if (V) Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent"
+ item.totalCurrent + "; totalTotal=" + item.totalTotal);
}
}
cursor.close();
// Add the notifications
for (NotificationItem item : mNotifications.values()) {
// Build the RemoteView object
RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME,
R.layout.status_bar_ongoing_event_progress_bar);
expandedView.setTextViewText(R.id.description, item.description);
expandedView.setProgressBar(R.id.progress_bar, item.totalTotal, item.totalCurrent,
item.totalTotal == -1);
expandedView.setTextViewText(R.id.progress_text, BluetoothOppUtility
.formatProgressText(item.totalTotal, item.totalCurrent));
// Build the notification object
Notification n = new Notification();
if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) {
n.icon = android.R.drawable.stat_sys_upload;
expandedView.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_upload);
} else if (item.direction == BluetoothShare.DIRECTION_INBOUND) {
n.icon = android.R.drawable.stat_sys_download;
expandedView.setImageViewResource(R.id.appIcon,
android.R.drawable.stat_sys_download);
} else {
if (V) Log.v(TAG, "mDirection ERROR!");
}
n.flags |= Notification.FLAG_ONGOING_EVENT;
n.contentView = expandedView;
Intent intent = new Intent(Constants.ACTION_LIST);
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id));
n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNotificationMgr.notify(item.id, n);
mActiveNotificationId = item.id;
}
}
private void updateCompletedNotification() {
String title;
String caption;
long timeStamp = 0;
int outboundSuccNumber = 0;
int outboundFailNumber = 0;
int outboundNum;
int inboundNum;
int inboundSuccNumber = 0;
int inboundFailNumber = 0;
Intent intent;
// If there is active transfer, no need to update complete transfer
// notification
if (!mUpdateCompleteNotification) {
if (V) Log.v(TAG, "No need to update complete notification");
return;
}
// After merge complete notifications to 2 notifications, there is no
// chance to update the active notifications to complete notifications
// as before. So need cancel the active notification after the active
// transfer becomes complete.
if (mNotificationMgr != null && mActiveNotificationId != 0) {
mNotificationMgr.cancel(mActiveNotificationId);
if (V) Log.v(TAG, "ongoing transfer notification was removed");
}
// Creating outbound notification
Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,
WHERE_COMPLETED_OUTBOUND, null, BluetoothShare.TIMESTAMP + " DESC");
if (cursor == null) {
return;
}
final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
final int statusIndex = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
if (cursor.isFirst()) {
// Display the time for the latest transfer
timeStamp = cursor.getLong(timestampIndex);
}
int status = cursor.getInt(statusIndex);
if (BluetoothShare.isStatusError(status)) {
outboundFailNumber++;
} else {
outboundSuccNumber++;
}
}
if (V) Log.v(TAG, "outbound: succ-" + outboundSuccNumber + " fail-" + outboundFailNumber);
cursor.close();
outboundNum = outboundSuccNumber + outboundFailNumber;
// create the outbound notification
if (outboundNum > 0) {
mOutNoti = new Notification();
mOutNoti.icon = android.R.drawable.stat_sys_upload_done;
title = mContext.getString(R.string.outbound_noti_title);
caption = mContext.getString(R.string.noti_caption, outboundSuccNumber,
outboundFailNumber);
intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER);
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
mOutNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
mContext, 0, intent, 0));
mOutNoti.when = timeStamp;
mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND, mOutNoti);
} else {
if (mNotificationMgr != null && mOutNoti != null) {
mNotificationMgr.cancel(NOTIFICATION_ID_OUTBOUND);
mOutNoti = null;
if (V) Log.v(TAG, "outbound notification was removed.");
}
}
// Creating inbound notification
cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,
WHERE_COMPLETED_INBOUND, null, BluetoothShare.TIMESTAMP + " DESC");
if (cursor == null) {
return;
}
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
if (cursor.isFirst()) {
// Display the time for the latest transfer
timeStamp = cursor.getLong(timestampIndex);
}
int status = cursor.getInt(statusIndex);
if (BluetoothShare.isStatusError(status)) {
inboundFailNumber++;
} else {
inboundSuccNumber++;
}
}
if (V) Log.v(TAG, "inbound: succ-" + inboundSuccNumber + " fail-" + inboundFailNumber);
cursor.close();
inboundNum = inboundSuccNumber + inboundFailNumber;
// create the inbound notification
if (inboundNum > 0) {
mInNoti = new Notification();
mInNoti.icon = android.R.drawable.stat_sys_download_done;
title = mContext.getString(R.string.inbound_noti_title);
caption = mContext.getString(R.string.noti_caption, inboundSuccNumber,
inboundFailNumber);
intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER);
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
mInNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
mContext, 0, intent, 0));
mInNoti.when = timeStamp;
mNotificationMgr.notify(NOTIFICATION_ID_INBOUND, mInNoti);
} else {
if (mNotificationMgr != null && mInNoti != null) {
mNotificationMgr.cancel(NOTIFICATION_ID_INBOUND);
mInNoti = null;
if (V) Log.v(TAG, "inbound notification was removed.");
}
}
}
private void updateIncomingFileConfirmNotification() {
Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,
WHERE_CONFIRM_PENDING, null, BluetoothShare._ID);
if (cursor == null) {
return;
}
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
String title = mContext.getString(R.string.incoming_file_confirm_Notification_title);
String caption = mContext
.getString(R.string.incoming_file_confirm_Notification_caption);
int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
Notification n = new Notification();
n.icon = R.drawable.bt_incomming_file_notification;
n.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
n.defaults = Notification.DEFAULT_SOUND;
n.tickerText = title;
Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
intent.setData(contentUri);
n.when = timeStamp;
n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,
intent, 0));
intent = new Intent(Constants.ACTION_HIDE);
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
intent.setData(contentUri);
n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNotificationMgr.notify(id, n);
}
cursor.close();
}
}