summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLixin Yue <L.X.YUE@motorola.com>2010-02-26 17:35:23 +0800
committerMichael Chan <mchan@android.com>2010-03-03 19:13:06 -0800
commit5cc617943765df27844e459362c4bc1821305216 (patch)
treedb27b2c62dfa1db4ec0271483a0cd00c3b4ddfd9
parent02a85f25c5410e6e104c25ec3b109797db0aa18a (diff)
downloadandroid_packages_apps_Bluetooth-5cc617943765df27844e459362c4bc1821305216.tar.gz
android_packages_apps_Bluetooth-5cc617943765df27844e459362c4bc1821305216.tar.bz2
android_packages_apps_Bluetooth-5cc617943765df27844e459362c4bc1821305216.zip
Add opp Transfer history to avoid cluster of notificatoins To reduce the cluster of Opp nofications, we introduce the concept of Opp transfer history. Only 2 notifications exist for finished opp transfers, one is for outbound transfers, and another is for inbound transfers. User can see all detailed items in corresponding screen when click the notification.
Change-Id: Iffed353ea6b0d7c958c71fe8d3996937058ced30
-rw-r--r--AndroidManifest.xml5
-rw-r--r--res/layout/bluetooth_transfer_item.xml71
-rw-r--r--res/layout/bluetooth_transfers_page.xml30
-rw-r--r--res/layout/no_transfers.xml23
-rw-r--r--res/menu/transferhistory.xml22
-rw-r--r--res/menu/transferhistorycontextfinished.xml24
-rw-r--r--res/values/strings.xml17
-rw-r--r--src/com/android/bluetooth/opp/BluetoothOppNotification.java228
-rw-r--r--src/com/android/bluetooth/opp/BluetoothOppReceiver.java14
-rw-r--r--src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java130
-rw-r--r--src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java268
-rw-r--r--src/com/android/bluetooth/opp/Constants.java6
12 files changed, 787 insertions, 51 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 298e1f83a..f54bfea19 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -95,6 +95,11 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name=".opp.BluetoothOppTransferHistory"
+ android:label=""
+ android:excludeFromRecents="true"
+ android:configChanges="orientation|keyboardHidden">
+ </activity>
<activity android:name=".pbap.BluetoothPbapActivity"
android:excludeFromRecents="true"
android:theme="@*android:style/Theme.Dialog.Alert">
diff --git a/res/layout/bluetooth_transfer_item.xml b/res/layout/bluetooth_transfer_item.xml
new file mode 100644
index 000000000..a5c5a39e2
--- /dev/null
+++ b/res/layout/bluetooth_transfer_item.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/transfer_icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:scaleType="fitCenter"
+ />
+
+ <TextView android:id="@+id/transfer_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@id/transfer_icon"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+
+ <TextView android:id="@+id/targetdevice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/transfer_icon"
+ android:layout_below="@id/transfer_title"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/complete_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_below="@id/targetdevice"
+ android:layout_alignParentRight="true"
+ android:visibility="gone"
+ />
+
+ <TextView android:id="@+id/complete_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/targetdevice"
+ android:layout_toRightOf="@id/transfer_icon"
+ android:layout_toLeftOf="@id/complete_date"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:visibility="gone"
+ />
+</RelativeLayout>
+
diff --git a/res/layout/bluetooth_transfers_page.xml b/res/layout/bluetooth_transfers_page.xml
new file mode 100644
index 000000000..10e6c98b6
--- /dev/null
+++ b/res/layout/bluetooth_transfers_page.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+ <ViewStub
+ android:id="@+id/empty"
+ android:layout="@layout/no_transfers"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+</merge>
diff --git a/res/layout/no_transfers.xml b/res/layout/no_transfers.xml
new file mode 100644
index 000000000..ee30c31ab
--- /dev/null
+++ b/res/layout/no_transfers.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:text="@string/no_transfers"
+ android:gravity="center"
+ android:textStyle="bold"
+ />
diff --git a/res/menu/transferhistory.xml b/res/menu/transferhistory.xml
new file mode 100644
index 000000000..0ed408944
--- /dev/null
+++ b/res/menu/transferhistory.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/transfer_menu_clear_all"
+ android:title="@string/transfer_menu_clear_all"
+ android:icon="@*android:drawable/ic_menu_clear_playlist" />
+</menu>
diff --git a/res/menu/transferhistorycontextfinished.xml b/res/menu/transferhistorycontextfinished.xml
new file mode 100644
index 000000000..3a6029af4
--- /dev/null
+++ b/res/menu/transferhistorycontextfinished.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/transfer_menu_open"
+ android:title="@string/transfer_menu_open" />
+ <item android:id="@+id/transfer_menu_clear"
+ android:title="@string/transfer_menu_clear" />
+
+</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3b9b22c3c..89e6c0634 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -172,4 +172,21 @@
<!-- Bluetooth OPP Live Folder -->
<string name="btopp_live_folder">Bluetooth received</string>
+ <!-- Bluetooth OPP Transfer History -->
+ <string name="download_success"> %1$s Received complete.</string>
+ <string name="upload_success"> %1$s Sent complete.</string>
+ <string name="inbound_history_title">Inbound transfers</string>
+ <string name="outbound_history_title">Outbound transfers</string>
+ <string name="no_transfers">Transfer history is empty.</string>
+ <string name="transfer_clear_dlg_msg">All items will be cleared from the list.</string>
+ <string name="outbound_noti_title">Bluetooth share: Sent files</string>
+ <string name="inbound_noti_title">Bluetooth share: Received files</string>
+ <string name="noti_title">Bluetooth share</string>
+ <string name="noti_caption"> %1$s successful, %2$s failed.</string>
+
+ <string name="transfer_menu_clear_all">Clear list</string>
+ <string name="transfer_menu_open">Open</string>
+ <string name="transfer_menu_clear">Clear from list</string>
+ <string name="transfer_clear_dlg_title">Clear</string>
+
</resources>
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 0076c8e0c..80d7a72a5 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -70,6 +70,12 @@ class BluetoothOppNotification {
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;
@@ -85,6 +91,18 @@ class BluetoothOppNotification {
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.
*/
@@ -169,19 +187,35 @@ class BluetoothOppNotification {
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(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
- int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
- int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
- int total = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
- int current = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
+ 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(cursor.getColumnIndexOrThrow(BluetoothShare._DATA));
+ String fileName = cursor.getString(dataIndex);
if (fileName == null) {
- fileName = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+ fileName = cursor.getString(filenameHintIndex);
}
if (fileName == null) {
fileName = mContext.getString(R.string.unknown_file);
@@ -252,69 +286,161 @@ class BluetoothOppNotification {
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, null, BluetoothShare._ID);
+ WHERE_COMPLETED_OUTBOUND, null, BluetoothShare.TIMESTAMP + " DESC");
if (cursor == null) {
return;
}
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- // Add the notifications
- long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
- int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
- int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
- int status = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
+ final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
+ final int statusIndex = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
- String fileName = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
- if (fileName == null) {
- fileName = mContext.getString(R.string.unknown_file);
+ 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);
- String title;
- String caption;
- Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
-
- Notification n = new Notification();
if (BluetoothShare.isStatusError(status)) {
- if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
- title = mContext.getString(R.string.notification_sent_fail, fileName);
- } else {
- title = mContext.getString(R.string.notification_received_fail, fileName);
- }
- caption = mContext.getString(R.string.download_fail_line3, BluetoothOppUtility
- .getStatusDescription(mContext, status));
- n.icon = android.R.drawable.stat_notify_error;
+ outboundFailNumber++;
} else {
- if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
- title = mContext.getString(R.string.notification_sent, fileName);
- n.icon = android.R.drawable.stat_sys_upload_done;
- } else {
- title = mContext.getString(R.string.notification_received, fileName);
- n.icon = android.R.drawable.stat_sys_download_done;
- }
- caption = mContext.getString(R.string.notification_sent_complete);
+ outboundSuccNumber++;
}
- Intent intent = new Intent(Constants.ACTION_OPEN);
- intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
- intent.setData(contentUri);
+ }
+ if (V) Log.v(TAG, "outbound: succ-" + outboundSuccNumber + " fail-" + outboundFailNumber);
+ cursor.close();
- n.when = timeStamp;
- n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,
- intent, 0));
+ outboundNum = outboundSuccNumber + outboundFailNumber;
+ title = mContext.getString(R.string.outbound_noti_title);
+ intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER);
+ intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
+
+ // create the outbound notification
+ if (mOutNoti == null && outboundNum > 0) {
+ mOutNoti = new Notification();
+ mOutNoti.icon = android.R.drawable.stat_sys_upload_done;
+ caption = mContext.getString(R.string.noti_caption, outboundSuccNumber,
+ outboundFailNumber);
+ mOutNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
+ mContext, 0, intent, 0));
+ mOutNoti.when = timeStamp;
+ // To make number take effect, must set to 1 when creating the
+ // notification
+ mOutNoti.number = 1;
+ mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND, mOutNoti);
+ }
- 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);
+ // update the outbound notification
+ if (outboundNum > 0) {
+ caption = mContext.getString(R.string.noti_caption, outboundSuccNumber,
+ outboundFailNumber);
+ mOutNoti.when = timeStamp;
+ mOutNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
+ mContext, 0, intent, 0));
+ mOutNoti.number = outboundNum;
+ 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.");
+ }
+ }
- mNotificationMgr.notify(id, n);
+ // 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;
+ title = mContext.getString(R.string.inbound_noti_title);
+ intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER);
+ intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
+
+ // create the inbound notification
+ if (mInNoti == null && inboundNum > 0) {
+ mInNoti = new Notification();
+ mInNoti.icon = android.R.drawable.stat_sys_download_done;
+ caption = mContext.getString(R.string.noti_caption, inboundSuccNumber,
+ inboundFailNumber);
+ mInNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
+ mContext, 0, intent, 0));
+ mInNoti.when = timeStamp;
+ // To make number take effect, must set to 1 when creating the
+ // notification
+ mInNoti.number = 1;
+ mNotificationMgr.notify(NOTIFICATION_ID_INBOUND, mInNoti);
+ }
+
+ // update the inbound notification
+ if (inboundNum > 0) {
+ caption = mContext.getString(R.string.noti_caption, inboundSuccNumber,
+ inboundFailNumber);
+ mInNoti.when = timeStamp;
+ mInNoti.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(
+ mContext, 0, intent, 0));
+ mInNoti.number = inboundNum;
+ 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() {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index a0735d0c6..870039078 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -169,6 +169,20 @@ public class BluetoothOppReceiver extends BroadcastReceiver {
notMgr.cancel((int)ContentUris.parseId(intent.getData()));
if (V) Log.v(TAG, "notMgr.cancel called");
}
+ } else if (action.equals(Constants.ACTION_OPEN_OUTBOUND_TRANSFER)) {
+ if (V) Log.v(TAG, "Received ACTION_OPEN_OUTBOUND_TRANSFER.");
+
+ Intent in = new Intent(context, BluetoothOppTransferHistory.class);
+ in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ in.putExtra("direction", BluetoothShare.DIRECTION_OUTBOUND);
+ context.startActivity(in);
+ } else if (action.equals(Constants.ACTION_OPEN_INBOUND_TRANSFER)) {
+ if (V) Log.v(TAG, "Received ACTION_OPEN_INBOUND_TRANSFER.");
+
+ Intent in = new Intent(context, BluetoothOppTransferHistory.class);
+ in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ in.putExtra("direction", BluetoothShare.DIRECTION_INBOUND);
+ context.startActivity(in);
} else if (action.equals(Constants.ACTION_HIDE)) {
if (V) Log.v(TAG, "Receiver hide for " + intent.getData());
Cursor cursor = context.getContentResolver().query(intent.getData(), null, null, null,
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
new file mode 100644
index 000000000..dfdd70985
--- /dev/null
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * 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.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.text.format.DateUtils;
+import android.text.format.DateFormat;
+import android.text.format.Formatter;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import java.util.Date;
+
+/**
+ * This class is used to represent the data for the transfer history list box.
+ * The only real work done by this class is to construct a custom view for the
+ * line items.
+ */
+public class BluetoothOppTransferAdapter extends ResourceCursorAdapter {
+ private Context mContext;
+
+ public BluetoothOppTransferAdapter(Context context, int layout, Cursor c) {
+ super(context, layout, c);
+ mContext = context;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ Resources r = context.getResources();
+
+ // Retrieve the icon for this transfer
+ ImageView iv = (ImageView)view.findViewById(R.id.transfer_icon);
+ int status = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
+ int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
+ if (BluetoothShare.isStatusError(status)) {
+ iv.setImageResource(android.R.drawable.stat_notify_error);
+ } else {
+ if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
+ iv.setImageResource(android.R.drawable.stat_sys_upload_done);
+ } else {
+ iv.setImageResource(android.R.drawable.stat_sys_download_done);
+ }
+ }
+
+ // Set title
+ TextView tv = (TextView)view.findViewById(R.id.transfer_title);
+ String title = cursor.getString(
+ cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+ if (title == null) {
+ title = mContext.getString(R.string.unknown_file);
+ }
+ tv.setText(title);
+
+ // target device
+ tv = (TextView)view.findViewById(R.id.targetdevice);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ int destinationColumnId = cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION);
+ BluetoothDevice remoteDevice = adapter.getRemoteDevice(cursor
+ .getString(destinationColumnId));
+ String deviceName = BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
+ tv.setText(deviceName);
+
+ // complete text and complete date
+ long totalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
+ if (BluetoothShare.isStatusCompleted(status)) {
+ tv = (TextView)view.findViewById(R.id.complete_text);
+ tv.setVisibility(View.VISIBLE);
+ if (BluetoothShare.isStatusError(status)) {
+ tv.setText(BluetoothOppUtility.getStatusDescription(mContext, status));
+ } else {
+ String completeText;
+ if (dir == BluetoothShare.DIRECTION_INBOUND) {
+ completeText = r.getString(R.string.download_success, Formatter.formatFileSize(
+ mContext, totalBytes));
+ } else {
+ completeText = r.getString(R.string.upload_success, Formatter.formatFileSize(
+ mContext, totalBytes));
+ }
+ tv.setText(completeText);
+ }
+
+ int dateColumnId = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
+ long time = cursor.getLong(dateColumnId);
+ Date d = new Date(time);
+ CharSequence str = DateUtils.isToday(time) ? DateFormat.getTimeFormat(mContext).format(
+ d) : DateFormat.getDateFormat(mContext).format(d);
+ tv = (TextView)view.findViewById(R.id.complete_date);
+ tv.setVisibility(View.VISIBLE);
+ tv.setText(str);
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
new file mode 100644
index 000000000..591d556f0
--- /dev/null
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
@@ -0,0 +1,268 @@
+/*
+ * 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.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * View showing the user's finished bluetooth opp transfers that the user does
+ * not confirm. Including outbound and inbound transfers, both successful and
+ * failed. *
+ */
+public class BluetoothOppTransferHistory extends Activity implements
+ View.OnCreateContextMenuListener, OnItemClickListener {
+ private static final String TAG = "BluetoothOppTransferHistory";
+
+ private static final boolean D = Constants.DEBUG;
+
+ private static final boolean V = Constants.VERBOSE;
+
+ private ListView mListView;
+
+ private Cursor mTransferCursor;
+
+ private BluetoothOppTransferAdapter mTransferAdapter;
+
+ private int mIdColumnId;
+
+ private int mContextMenuPosition;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.bluetooth_transfers_page);
+ mListView = (ListView)findViewById(R.id.list);
+ mListView.setEmptyView(findViewById(R.id.empty));
+
+ String direction;
+ int dir = getIntent().getIntExtra("direction", 0);
+ if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
+ setTitle(getText(R.string.outbound_history_title));
+ direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND
+ + ")";
+ } else {
+ setTitle(getText(R.string.inbound_history_title));
+ direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND
+ + ")";
+ }
+
+ final String selection = BluetoothShare.STATUS + " >= '200' AND " + "("
+ + BluetoothShare.VISIBILITY + " IS NULL OR " + BluetoothShare.VISIBILITY + " == '"
+ + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")" + " AND " + direction;
+ final String sortOrder = BluetoothShare.TIMESTAMP + " DESC";
+
+ mTransferCursor = managedQuery(BluetoothShare.CONTENT_URI, new String[] {
+ "_id", BluetoothShare.FILENAME_HINT, BluetoothShare.STATUS,
+ BluetoothShare.TOTAL_BYTES, BluetoothShare._DATA, BluetoothShare.TIMESTAMP,
+ BluetoothShare.VISIBILITY, BluetoothShare.DESTINATION, BluetoothShare.DIRECTION
+ }, selection, sortOrder);
+
+ // only attach everything to the listbox if we can access
+ // the transfer database. Otherwise, just show it empty
+ if (mTransferCursor != null) {
+ mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID);
+ // Create a list "controller" for the data
+ mTransferAdapter = new BluetoothOppTransferAdapter(this,
+ R.layout.bluetooth_transfer_item, mTransferCursor);
+ mListView.setAdapter(mTransferAdapter);
+ mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
+ mListView.setOnCreateContextMenuListener(this);
+ mListView.setOnItemClickListener(this);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mTransferCursor != null) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.transferhistory, menu);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean showClear = getClearableCount() > 0;
+ menu.findItem(R.id.transfer_menu_clear_all).setEnabled(showClear);
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.transfer_menu_clear_all:
+ promptClearList();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ mTransferCursor.moveToPosition(mContextMenuPosition);
+ switch (item.getItemId()) {
+ case R.id.transfer_menu_open:
+ openCompleteTransfer();
+ return true;
+
+ case R.id.transfer_menu_clear:
+ int sessionId = mTransferCursor.getInt(mIdColumnId);
+ Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
+ BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ if (mTransferCursor != null) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+ mTransferCursor.moveToPosition(info.position);
+ mContextMenuPosition = info.position;
+
+ String fileName = mTransferCursor.getString(mTransferCursor
+ .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+ if (fileName == null) {
+ fileName = this.getString(R.string.unknown_file);
+ }
+ menu.setHeaderTitle(fileName);
+
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.transferhistorycontextfinished, menu);
+ }
+ }
+
+ /**
+ * Prompt the user if they would like to clear the transfer history
+ */
+ private void promptClearList() {
+ new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title).setMessage(
+ R.string.transfer_clear_dlg_msg).setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ clearAllDownloads();
+ }
+ }).setNegativeButton(android.R.string.cancel, null).show();
+ }
+
+ /**
+ * Get the number of finished transfers, including error and success.
+ */
+ private int getClearableCount() {
+ int count = 0;
+ if (mTransferCursor.moveToFirst()) {
+ while (!mTransferCursor.isAfterLast()) {
+ int statusColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
+ int status = mTransferCursor.getInt(statusColumnId);
+ if (BluetoothShare.isStatusCompleted(status)) {
+ count++;
+ }
+ mTransferCursor.moveToNext();
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Clear all finished transfers, error and success transfer items.
+ */
+ private void clearAllDownloads() {
+ if (mTransferCursor.moveToFirst()) {
+ while (!mTransferCursor.isAfterLast()) {
+ int sessionId = mTransferCursor.getInt(mIdColumnId);
+ Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
+ BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
+
+ mTransferCursor.moveToNext();
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget
+ * .AdapterView, android.view.View, int, long)
+ */
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ // Open the selected item
+ mTransferCursor.moveToPosition(position);
+ openCompleteTransfer();
+ }
+
+ /**
+ * Open the selected finished transfer. mDownloadCursor must be moved to
+ * appropriate position before calling this function
+ */
+ private void openCompleteTransfer() {
+ int sessionId = mTransferCursor.getInt(mIdColumnId);
+ Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
+ BluetoothOppTransferInfo transInfo = new BluetoothOppTransferInfo();
+ transInfo = BluetoothOppUtility.queryRecord(this, contentUri);
+ if (transInfo == null) {
+ Log.e(TAG, "Error: Can not get data from db");
+ return;
+ }
+ if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND
+ && BluetoothShare.isStatusSuccess(transInfo.mStatus)) {
+ // if received file successfully, open this file
+ BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
+ BluetoothOppUtility.openReceivedFile(this, transInfo.mFileName, transInfo.mFileType,
+ transInfo.mTimeStamp, contentUri);
+ } else {
+ Intent in = new Intent(this, BluetoothOppTransferActivity.class);
+ in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ in.setData(contentUri);
+ this.startActivity(in);
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index 2356eada2..fe5d87912 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -59,6 +59,12 @@ public class Constants {
/** the intent that gets sent when clicking a successful transfer */
public static final String ACTION_OPEN = "android.btopp.intent.action.OPEN";
+ /** the intent that gets sent when clicking outbound transfer notification */
+ public static final String ACTION_OPEN_OUTBOUND_TRANSFER = "android.btopp.intent.action.OPEN_OUTBOUND";
+
+ /** the intent that gets sent when clicking a inbound transfer notification */
+ public static final String ACTION_OPEN_INBOUND_TRANSFER = "android.btopp.intent.action.OPEN_INBOUND";
+
/** the intent that gets sent when clicking an incomplete/failed transfer */
public static final String ACTION_LIST = "android.btopp.intent.action.LIST";