OSDN Git Service

Add opp Transfer history to avoid cluster of notificatoins To reduce the cluster...
authorLixin Yue <L.X.YUE@motorola.com>
Fri, 26 Feb 2010 09:35:23 +0000 (17:35 +0800)
committerMichael Chan <mchan@android.com>
Thu, 4 Mar 2010 03:13:06 +0000 (19:13 -0800)
Change-Id: Iffed353ea6b0d7c958c71fe8d3996937058ced30

12 files changed:
AndroidManifest.xml
res/layout/bluetooth_transfer_item.xml [new file with mode: 0644]
res/layout/bluetooth_transfers_page.xml [new file with mode: 0644]
res/layout/no_transfers.xml [new file with mode: 0644]
res/menu/transferhistory.xml [new file with mode: 0644]
res/menu/transferhistorycontextfinished.xml [new file with mode: 0644]
res/values/strings.xml
src/com/android/bluetooth/opp/BluetoothOppNotification.java
src/com/android/bluetooth/opp/BluetoothOppReceiver.java
src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java [new file with mode: 0644]
src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java [new file with mode: 0644]
src/com/android/bluetooth/opp/Constants.java

index 298e1f8..f54bfea 100644 (file)
                 <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 (file)
index 0000000..a5c5a39
--- /dev/null
@@ -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 (file)
index 0000000..10e6c98
--- /dev/null
@@ -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 (file)
index 0000000..ee30c31
--- /dev/null
@@ -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 (file)
index 0000000..0ed4089
--- /dev/null
@@ -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 (file)
index 0000000..3a6029a
--- /dev/null
@@ -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>
index 3b9b22c..89e6c06 100644 (file)
     <!-- 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>
index 0076c8e..80d7a72 100644 (file)
@@ -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() {
index a0735d0..8700390 100644 (file)
@@ -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 (file)
index 0000000..dfdd709
--- /dev/null
@@ -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 (file)
index 0000000..591d556
--- /dev/null
@@ -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);
+        }
+    }
+}
index 2356ead..fe5d879 100644 (file)
@@ -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";