<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">
--- /dev/null
+<?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>
+
--- /dev/null
+<?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>
--- /dev/null
+<?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"
+ />
--- /dev/null
+<?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>
--- /dev/null
+<?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>
<!-- 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>
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;
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.
*/
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);
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() {
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,
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
/** 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";