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 android.app.Activity; 36 import android.app.AlertDialog; 37 import android.bluetooth.BluetoothAdapter; 38 import android.content.DialogInterface; 39 import android.content.Intent; 40 import android.database.Cursor; 41 import android.database.StaleDataException; 42 import android.net.Uri; 43 import android.os.Bundle; 44 import android.util.Log; 45 import android.view.ContextMenu; 46 import android.view.ContextMenu.ContextMenuInfo; 47 import android.view.Menu; 48 import android.view.MenuInflater; 49 import android.view.MenuItem; 50 import android.view.View; 51 import android.widget.AdapterView; 52 import android.widget.AdapterView.OnItemClickListener; 53 import android.widget.ListView; 54 55 import com.android.bluetooth.R; 56 57 /** 58 * View showing the user's finished bluetooth opp transfers that the user does 59 * not confirm. Including outbound and inbound transfers, both successful and 60 * failed. * 61 */ 62 public class BluetoothOppTransferHistory extends Activity 63 implements View.OnCreateContextMenuListener, OnItemClickListener { 64 private static final String TAG = "BluetoothOppTransferHistory"; 65 66 private static final boolean V = Constants.VERBOSE; 67 68 private ListView mListView; 69 70 private Cursor mTransferCursor; 71 72 private BluetoothOppTransferAdapter mTransferAdapter; 73 74 private int mIdColumnId; 75 76 private int mContextMenuPosition; 77 78 private boolean mShowAllIncoming; 79 80 private boolean mContextMenu = false; 81 82 /** Class to handle Notification Manager updates */ 83 private BluetoothOppNotification mNotifier; 84 85 @Override onCreate(Bundle icicle)86 public void onCreate(Bundle icicle) { 87 super.onCreate(icicle); 88 setContentView(R.layout.bluetooth_transfers_page); 89 mListView = (ListView) findViewById(R.id.list); 90 mListView.setEmptyView(findViewById(R.id.empty)); 91 92 mShowAllIncoming = getIntent().getBooleanExtra(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 (" + BluetoothShare.VISIBILITY + " IS NULL OR " 114 + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE 115 + "')"; 116 } 117 118 final String sortOrder = BluetoothShare.TIMESTAMP + " DESC"; 119 120 mTransferCursor = getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{ 121 "_id", 122 BluetoothShare.FILENAME_HINT, 123 BluetoothShare.STATUS, 124 BluetoothShare.TOTAL_BYTES, 125 BluetoothShare._DATA, 126 BluetoothShare.TIMESTAMP, 127 BluetoothShare.VISIBILITY, 128 BluetoothShare.DESTINATION, 129 BluetoothShare.DIRECTION 130 }, selection, null, sortOrder); 131 132 // only attach everything to the listbox if we can access 133 // the transfer database. Otherwise, just show it empty 134 if (mTransferCursor != null) { 135 mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID); 136 // Create a list "controller" for the data 137 mTransferAdapter = 138 new BluetoothOppTransferAdapter(this, R.layout.bluetooth_transfer_item, 139 mTransferCursor); 140 mListView.setAdapter(mTransferAdapter); 141 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); 142 mListView.setOnCreateContextMenuListener(this); 143 mListView.setOnItemClickListener(this); 144 } 145 146 mNotifier = new BluetoothOppNotification(this); 147 mContextMenu = false; 148 } 149 150 @Override onCreateOptionsMenu(Menu menu)151 public boolean onCreateOptionsMenu(Menu menu) { 152 if (mTransferCursor != null && !mShowAllIncoming) { 153 MenuInflater inflater = getMenuInflater(); 154 inflater.inflate(R.menu.transferhistory, menu); 155 } 156 return true; 157 } 158 159 @Override onPrepareOptionsMenu(Menu menu)160 public boolean onPrepareOptionsMenu(Menu menu) { 161 if (!mShowAllIncoming) { 162 menu.findItem(R.id.transfer_menu_clear_all).setEnabled(isTransferComplete()); 163 } 164 return super.onPrepareOptionsMenu(menu); 165 } 166 167 @Override onOptionsItemSelected(MenuItem item)168 public boolean onOptionsItemSelected(MenuItem item) { 169 switch (item.getItemId()) { 170 case R.id.transfer_menu_clear_all: 171 promptClearList(); 172 return true; 173 } 174 return false; 175 } 176 177 @Override onContextItemSelected(MenuItem item)178 public boolean onContextItemSelected(MenuItem item) { 179 if (mTransferCursor.getCount() == 0) { 180 Log.i(TAG, "History is already cleared, not clearing again"); 181 return true; 182 } 183 mTransferCursor.moveToPosition(mContextMenuPosition); 184 switch (item.getItemId()) { 185 case R.id.transfer_menu_open: 186 openCompleteTransfer(); 187 updateNotificationWhenBtDisabled(); 188 return true; 189 190 case R.id.transfer_menu_clear: 191 int sessionId = mTransferCursor.getInt(mIdColumnId); 192 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 193 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 194 updateNotificationWhenBtDisabled(); 195 return true; 196 } 197 return false; 198 } 199 200 @Override onDestroy()201 protected void onDestroy() { 202 if (mTransferCursor != null) { 203 mTransferCursor.close(); 204 } 205 super.onDestroy(); 206 } 207 208 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)209 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 210 if (mTransferCursor != null) { 211 mContextMenu = true; 212 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; 213 mTransferCursor.moveToPosition(info.position); 214 mContextMenuPosition = info.position; 215 216 String fileName = mTransferCursor.getString( 217 mTransferCursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 218 if (fileName == null) { 219 fileName = this.getString(R.string.unknown_file); 220 } 221 menu.setHeaderTitle(fileName); 222 223 MenuInflater inflater = getMenuInflater(); 224 if (mShowAllIncoming) { 225 inflater.inflate(R.menu.receivedfilescontextfinished, menu); 226 } else { 227 inflater.inflate(R.menu.transferhistorycontextfinished, menu); 228 } 229 } 230 } 231 232 /** 233 * Prompt the user if they would like to clear the transfer history 234 */ promptClearList()235 private void promptClearList() { 236 new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title) 237 .setMessage(R.string.transfer_clear_dlg_msg) 238 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 239 @Override 240 public void onClick(DialogInterface dialog, int whichButton) { 241 clearAllDownloads(); 242 } 243 }) 244 .setNegativeButton(android.R.string.cancel, null) 245 .show(); 246 } 247 248 /** 249 * Returns true if the device has finished transfers, including error and success. 250 */ isTransferComplete()251 private boolean isTransferComplete() { 252 try { 253 if (mTransferCursor.moveToFirst()) { 254 while (!mTransferCursor.isAfterLast()) { 255 int statusColumnId = mTransferCursor 256 .getColumnIndexOrThrow(BluetoothShare.STATUS); 257 int status = mTransferCursor.getInt(statusColumnId); 258 if (BluetoothShare.isStatusCompleted(status)) { 259 return true; 260 } 261 mTransferCursor.moveToNext(); 262 } 263 } 264 } catch (StaleDataException e) { 265 } 266 return false; 267 } 268 269 /** 270 * Clear all finished transfers, error and success transfer items. 271 */ clearAllDownloads()272 private void clearAllDownloads() { 273 if (mTransferCursor.moveToFirst()) { 274 while (!mTransferCursor.isAfterLast()) { 275 int sessionId = mTransferCursor.getInt(mIdColumnId); 276 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 277 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 278 279 mTransferCursor.moveToNext(); 280 } 281 updateNotificationWhenBtDisabled(); 282 } 283 } 284 285 /* 286 * (non-Javadoc) 287 * @see 288 * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget 289 * .AdapterView, android.view.View, int, long) 290 */ 291 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)292 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 293 // Open the selected item 294 if (V) { 295 Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu); 296 } 297 if (!mContextMenu) { 298 mTransferCursor.moveToPosition(position); 299 openCompleteTransfer(); 300 updateNotificationWhenBtDisabled(); 301 } 302 mContextMenu = false; 303 } 304 305 /** 306 * Open the selected finished transfer. mDownloadCursor must be moved to 307 * appropriate position before calling this function 308 */ openCompleteTransfer()309 private void openCompleteTransfer() { 310 int sessionId = mTransferCursor.getInt(mIdColumnId); 311 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId); 312 BluetoothOppTransferInfo transInfo = BluetoothOppUtility.queryRecord(this, contentUri); 313 if (transInfo == null) { 314 Log.e(TAG, "Error: Can not get data from db"); 315 return; 316 } 317 if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND 318 && BluetoothShare.isStatusSuccess(transInfo.mStatus)) { 319 // if received file successfully, open this file 320 BluetoothOppUtility.updateVisibilityToHidden(this, contentUri); 321 BluetoothOppUtility.openReceivedFile(this, transInfo.mFileName, transInfo.mFileType, 322 transInfo.mTimeStamp, contentUri); 323 } else { 324 Intent in = new Intent(this, BluetoothOppTransferActivity.class); 325 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 326 in.setDataAndNormalize(contentUri); 327 this.startActivity(in); 328 } 329 } 330 331 /** 332 * When Bluetooth is disabled, notification can not be updated by 333 * ContentObserver in OppService, so need update manually. 334 */ updateNotificationWhenBtDisabled()335 private void updateNotificationWhenBtDisabled() { 336 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 337 if (!adapter.isEnabled()) { 338 if (V) { 339 Log.v(TAG, "Bluetooth is not enabled, update notification manually."); 340 } 341 mNotifier.updateNotification(); 342 } 343 } 344 } 345