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