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