1 /* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.android.bluetooth.opp; 34 35 import com.android.bluetooth.R; 36 37 import android.app.Activity; 38 import android.app.AlertDialog; 39 import android.bluetooth.BluetoothAdapter; 40 import android.content.DialogInterface; 41 import android.content.Intent; 42 import android.database.Cursor; 43 import android.net.Uri; 44 import android.os.Bundle; 45 import android.util.Log; 46 import android.view.ContextMenu; 47 import android.view.Menu; 48 import android.view.MenuInflater; 49 import android.view.MenuItem; 50 import android.view.View; 51 import android.view.ContextMenu.ContextMenuInfo; 52 import android.widget.AdapterView; 53 import android.widget.ListView; 54 import android.widget.AdapterView.OnItemClickListener; 55 56 /** 57 * View showing the user's finished bluetooth opp transfers that the user does 58 * not confirm. Including outbound and inbound transfers, both successful and 59 * failed. * 60 */ 61 public class BluetoothOppTransferHistory extends Activity implements 62 View.OnCreateContextMenuListener, OnItemClickListener { 63 private static final String TAG = "BluetoothOppTransferHistory"; 64 65 private static final boolean V = Constants.VERBOSE; 66 67 private ListView mListView; 68 69 private Cursor mTransferCursor; 70 71 private BluetoothOppTransferAdapter mTransferAdapter; 72 73 private int mIdColumnId; 74 75 private int mContextMenuPosition; 76 77 private boolean mShowAllIncoming; 78 79 private boolean mContextMenu = false; 80 81 /** Class to handle Notification Manager updates */ 82 private BluetoothOppNotification mNotifier; 83 84 @Override 85 public void onCreate(Bundle icicle) { 86 super.onCreate(icicle); 87 setContentView(R.layout.bluetooth_transfers_page); 88 mListView = (ListView)findViewById(R.id.list); 89 mListView.setEmptyView(findViewById(R.id.empty)); 90 91 mShowAllIncoming = getIntent().getBooleanExtra( 92 Constants.EXTRA_SHOW_ALL_FILES, false); 93 94 String direction; 95 int dir = getIntent().getIntExtra("direction", 0); 96 if (dir == BluetoothShare.DIRECTION_OUTBOUND) { 97 setTitle(getText(R.string.outbound_history_title)); 98 direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND 99 + ")"; 100 } else { 101 if (mShowAllIncoming) { 102 setTitle(getText(R.string.btopp_live_folder)); 103 } else { 104 setTitle(getText(R.string.inbound_history_title)); 105 } 106 direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND 107 + ")"; 108 } 109 110 String selection = BluetoothShare.STATUS + " >= '200' AND " + direction; 111 112 if (!mShowAllIncoming) { 113 selection = selection + " AND (" 114 + BluetoothShare.VISIBILITY + " IS NULL OR " 115 + BluetoothShare.VISIBILITY + " == '" 116 + BluetoothShare.VISIBILITY_VISIBLE + "')"; 117 } 118 119 final String sortOrder = BluetoothShare.TIMESTAMP + " DESC"; 120 121 mTransferCursor = managedQuery(BluetoothShare.CONTENT_URI, new String[] { 122 "_id", BluetoothShare.FILENAME_HINT, BluetoothShare.STATUS, 123 BluetoothShare.TOTAL_BYTES, BluetoothShare._DATA, BluetoothShare.TIMESTAMP, 124 BluetoothShare.VISIBILITY, BluetoothShare.DESTINATION, BluetoothShare.DIRECTION 125 }, selection, sortOrder); 126 127 // only attach everything to the listbox if we can access 128 // the transfer database. Otherwise, just show it empty 129 if (mTransferCursor != null) { 130 mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID); 131 // Create a list "controller" for the data 132 mTransferAdapter = new BluetoothOppTransferAdapter(this, 133 R.layout.bluetooth_transfer_item, mTransferCursor); 134 mListView.setAdapter(mTransferAdapter); 135 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); 136 mListView.setOnCreateContextMenuListener(this); 137 mListView.setOnItemClickListener(this); 138 } 139 140 mNotifier = new BluetoothOppNotification(this); 141 mContextMenu = false; 142 } 143 144 @Override 145 public boolean onCreateOptionsMenu(Menu menu) { 146 if (mTransferCursor != null && !mShowAllIncoming) { 147 MenuInflater inflater = getMenuInflater(); 148 inflater.inflate(R.menu.transferhistory, menu); 149 } 150 return true; 151 } 152 153 @Override 154 public boolean onPrepareOptionsMenu(Menu menu) { 155 if (!mShowAllIncoming) { 156 boolean showClear = getClearableCount() > 0; 157 menu.findItem(R.id.transfer_menu_clear_all).setEnabled(showClear); 158 } 159 return super.onPrepareOptionsMenu(menu); 160 } 161 162 @Override 163 public boolean onOptionsItemSelected(MenuItem item) { 164 switch (item.getItemId()) { 165 case R.id.transfer_menu_clear_all: 166 promptClearList(); 167 return true; 168 } 169 return false; 170 } 171 172 @Override 173 public boolean onContextItemSelected(MenuItem item) { 174 mTransferCursor.moveToPosition(mContextMenuPosition); 175 switch (item.getItemId()) { 176 case R.id.transfer_menu_open: 177 openCompleteTransfer(); 178 updateNotificationWhenBtDisabled(); 179 return true; 180 181 case R.id.transfer_menu_clear: 182 int sessionId = mTransferCursor.getInt(mIdColumnId); 183 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 184 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 185 updateNotificationWhenBtDisabled(); 186 return true; 187 } 188 return false; 189 } 190 191 @Override 192 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 193 if (mTransferCursor != null) { 194 mContextMenu = true; 195 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; 196 mTransferCursor.moveToPosition(info.position); 197 mContextMenuPosition = info.position; 198 199 String fileName = mTransferCursor.getString(mTransferCursor 200 .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 201 if (fileName == null) { 202 fileName = this.getString(R.string.unknown_file); 203 } 204 menu.setHeaderTitle(fileName); 205 206 MenuInflater inflater = getMenuInflater(); 207 if (mShowAllIncoming) { 208 inflater.inflate(R.menu.receivedfilescontextfinished, menu); 209 } else { 210 inflater.inflate(R.menu.transferhistorycontextfinished, menu); 211 } 212 } 213 } 214 215 /** 216 * Prompt the user if they would like to clear the transfer history 217 */ 218 private void promptClearList() { 219 new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title).setMessage( 220 R.string.transfer_clear_dlg_msg).setPositiveButton(android.R.string.ok, 221 new DialogInterface.OnClickListener() { 222 public void onClick(DialogInterface dialog, int whichButton) { 223 clearAllDownloads(); 224 } 225 }).setNegativeButton(android.R.string.cancel, null).show(); 226 } 227 228 /** 229 * Get the number of finished transfers, including error and success. 230 */ 231 private int getClearableCount() { 232 int count = 0; 233 if (mTransferCursor.moveToFirst()) { 234 while (!mTransferCursor.isAfterLast()) { 235 int statusColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare.STATUS); 236 int status = mTransferCursor.getInt(statusColumnId); 237 if (BluetoothShare.isStatusCompleted(status)) { 238 count++; 239 } 240 mTransferCursor.moveToNext(); 241 } 242 } 243 return count; 244 } 245 246 /** 247 * Clear all finished transfers, error and success transfer items. 248 */ 249 private void clearAllDownloads() { 250 if (mTransferCursor.moveToFirst()) { 251 while (!mTransferCursor.isAfterLast()) { 252 int sessionId = mTransferCursor.getInt(mIdColumnId); 253 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 254 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 255 256 mTransferCursor.moveToNext(); 257 } 258 updateNotificationWhenBtDisabled(); 259 } 260 } 261 262 /* 263 * (non-Javadoc) 264 * @see 265 * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget 266 * .AdapterView, android.view.View, int, long) 267 */ 268 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 269 // Open the selected item 270 if (V) Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu); 271 if (!mContextMenu) { 272 mTransferCursor.moveToPosition(position); 273 openCompleteTransfer(); 274 updateNotificationWhenBtDisabled(); 275 } 276 mContextMenu = false; 277 } 278 279 /** 280 * Open the selected finished transfer. mDownloadCursor must be moved to 281 * appropriate position before calling this function 282 */ 283 private void openCompleteTransfer() { 284 int sessionId = mTransferCursor.getInt(mIdColumnId); 285 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 286 BluetoothOppTransferInfo transInfo = BluetoothOppUtility.queryRecord(this, contentUri); 287 if (transInfo == null) { 288 Log.e(TAG, "Error: Can not get data from db"); 289 return; 290 } 291 if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND 292 && BluetoothShare.isStatusSuccess(transInfo.mStatus)) { 293 // if received file successfully, open this file 294 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 295 BluetoothOppUtility.openReceivedFile(this, transInfo.mFileName, transInfo.mFileType, 296 transInfo.mTimeStamp, contentUri); 297 } else { 298 Intent in = new Intent(this, BluetoothOppTransferActivity.class); 299 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 300 in.setDataAndNormalize(contentUri); 301 this.startActivity(in); 302 } 303 } 304 305 /** 306 * When Bluetooth is disabled, notification can not be updated by 307 * ContentObserver in OppService, so need update manually. 308 */ 309 private void updateNotificationWhenBtDisabled() { 310 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 311 if (!adapter.isEnabled()) { 312 if (V) Log.v(TAG, "Bluetooth is not enabled, update notification manually."); 313 mNotifier.updateNotification(); 314 } 315 } 316 } 317