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 static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 36 37 import android.app.NotificationManager; 38 import android.bluetooth.AlertActivity; 39 import android.bluetooth.BluetoothProfile; 40 import android.bluetooth.BluetoothProtoEnums; 41 import android.content.DialogInterface; 42 import android.content.Intent; 43 import android.database.ContentObserver; 44 import android.net.Uri; 45 import android.os.Bundle; 46 import android.os.Handler; 47 import android.text.format.Formatter; 48 import android.util.Log; 49 import android.view.View; 50 import android.widget.ProgressBar; 51 import android.widget.TextView; 52 import android.widget.Toast; 53 54 import com.android.bluetooth.BluetoothStatsLog; 55 import com.android.bluetooth.R; 56 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 57 58 import com.google.common.annotations.VisibleForTesting; 59 60 /** 61 * Handle all transfer related dialogs: -Ongoing transfer -Receiving one file dialog -Sending one 62 * file dialog -sending multiple files dialog -Complete transfer -receive -receive success, will 63 * trigger corresponding handler -receive fail dialog -send -send success dialog -send fail dialog 64 * -Other dialogs - - DIALOG_RECEIVE_ONGOING will transition to DIALOG_RECEIVE_COMPLETE_SUCCESS or 65 * DIALOG_RECEIVE_COMPLETE_FAIL DIALOG_SEND_ONGOING will transition to DIALOG_SEND_COMPLETE_SUCCESS 66 * or DIALOG_SEND_COMPLETE_FAIL 67 */ 68 // Next tag value for ContentProfileErrorReportUtils.report(): 2 69 public class BluetoothOppTransferActivity extends AlertActivity 70 implements DialogInterface.OnClickListener { 71 private static final String TAG = "BluetoothOppTransferActivity"; 72 73 private Uri mUri; 74 75 // ongoing transfer-0 complete transfer-1 76 boolean mIsComplete; 77 78 private BluetoothOppTransferInfo mTransInfo; 79 80 private ProgressBar mProgressTransfer; 81 82 private TextView mPercentView; 83 84 private View mView = null; 85 86 private TextView mLine1View, mLine2View, mLine3View, mLine5View; 87 88 @VisibleForTesting int mWhichDialog; 89 90 // Dialogs definition: 91 // Receive progress dialog 92 public static final int DIALOG_RECEIVE_ONGOING = 0; 93 94 // Receive complete and success dialog 95 public static final int DIALOG_RECEIVE_COMPLETE_SUCCESS = 1; 96 97 // Receive complete and fail dialog: will display some fail reason 98 public static final int DIALOG_RECEIVE_COMPLETE_FAIL = 2; 99 100 // Send progress dialog 101 public static final int DIALOG_SEND_ONGOING = 3; 102 103 // Send complete and success dialog 104 public static final int DIALOG_SEND_COMPLETE_SUCCESS = 4; 105 106 // Send complete and fail dialog: will let user retry 107 public static final int DIALOG_SEND_COMPLETE_FAIL = 5; 108 109 /** Observer to get notified when the content observer's data changes */ 110 private BluetoothTransferContentObserver mObserver; 111 112 // do not update button during activity creating, only update when db 113 // changes after activity created 114 private boolean mNeedUpdateButton = false; 115 116 private class BluetoothTransferContentObserver extends ContentObserver { BluetoothTransferContentObserver()117 BluetoothTransferContentObserver() { 118 super(new Handler()); 119 } 120 121 @Override onChange(boolean selfChange)122 public void onChange(boolean selfChange) { 123 Log.v(TAG, "received db changes."); 124 mNeedUpdateButton = true; 125 updateProgressbar(); 126 } 127 } 128 129 @Override onCreate(Bundle savedInstanceState)130 protected void onCreate(Bundle savedInstanceState) { 131 super.onCreate(savedInstanceState); 132 133 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 134 Intent intent = getIntent(); 135 mUri = intent.getData(); 136 137 mTransInfo = new BluetoothOppTransferInfo(); 138 mTransInfo = BluetoothOppUtility.queryRecord(this, mUri); 139 if (mTransInfo == null) { 140 Log.e(TAG, "Error: Can not get data from db"); 141 ContentProfileErrorReportUtils.report( 142 BluetoothProfile.OPP, 143 BluetoothProtoEnums.BLUETOOTH_OPP_TRANSFER_ACTIVITY, 144 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 145 0); 146 finish(); 147 return; 148 } 149 150 mIsComplete = BluetoothShare.isStatusCompleted(mTransInfo.mStatus); 151 152 displayWhichDialog(); 153 154 // update progress bar for ongoing transfer 155 if (!mIsComplete) { 156 mObserver = new BluetoothTransferContentObserver(); 157 getContentResolver() 158 .registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver); 159 } 160 161 if (mWhichDialog != DIALOG_SEND_ONGOING && mWhichDialog != DIALOG_RECEIVE_ONGOING) { 162 // set this record to INVISIBLE 163 BluetoothOppUtility.updateVisibilityToHidden(this, mUri); 164 } 165 166 // Set up the "dialog" 167 setUpDialog(); 168 } 169 170 @Override onDestroy()171 protected void onDestroy() { 172 Log.d(TAG, "onDestroy()"); 173 174 if (mObserver != null) { 175 getContentResolver().unregisterContentObserver(mObserver); 176 } 177 super.onDestroy(); 178 } 179 displayWhichDialog()180 private void displayWhichDialog() { 181 int direction = mTransInfo.mDirection; 182 boolean isSuccess = BluetoothShare.isStatusSuccess(mTransInfo.mStatus); 183 boolean isComplete = BluetoothShare.isStatusCompleted(mTransInfo.mStatus); 184 185 if (direction == BluetoothShare.DIRECTION_INBOUND) { 186 if (isComplete) { 187 if (isSuccess) { 188 // should not go here 189 mWhichDialog = DIALOG_RECEIVE_COMPLETE_SUCCESS; 190 } else if (!isSuccess) { 191 mWhichDialog = DIALOG_RECEIVE_COMPLETE_FAIL; 192 } 193 } else if (!isComplete) { 194 mWhichDialog = DIALOG_RECEIVE_ONGOING; 195 } 196 } else if (direction == BluetoothShare.DIRECTION_OUTBOUND) { 197 if (isComplete) { 198 if (isSuccess) { 199 mWhichDialog = DIALOG_SEND_COMPLETE_SUCCESS; 200 201 } else if (!isSuccess) { 202 mWhichDialog = DIALOG_SEND_COMPLETE_FAIL; 203 } 204 } else if (!isComplete) { 205 mWhichDialog = DIALOG_SEND_ONGOING; 206 } 207 } 208 209 Log.v( 210 TAG, 211 " WhichDialog/dir/isComplete/failOrSuccess" 212 + mWhichDialog 213 + direction 214 + isComplete 215 + isSuccess); 216 } 217 setUpDialog()218 private void setUpDialog() { 219 mAlertBuilder.setTitle(getString(R.string.download_title)); 220 if ((mWhichDialog == DIALOG_RECEIVE_ONGOING) || (mWhichDialog == DIALOG_SEND_ONGOING)) { 221 mAlertBuilder.setPositiveButton(R.string.download_ok, this); 222 mAlertBuilder.setNegativeButton(R.string.download_cancel, this); 223 } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) { 224 mAlertBuilder.setPositiveButton(R.string.download_succ_ok, this); 225 } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) { 226 mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon); 227 mAlertBuilder.setPositiveButton(R.string.download_fail_ok, this); 228 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) { 229 mAlertBuilder.setPositiveButton(R.string.upload_succ_ok, this); 230 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) { 231 mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon); 232 mAlertBuilder.setNegativeButton(R.string.upload_fail_cancel, this); 233 } 234 mAlertBuilder.setView(createView()); 235 setupAlert(); 236 } 237 createView()238 private View createView() { 239 240 mView = getLayoutInflater().inflate(R.layout.file_transfer, null); 241 242 mProgressTransfer = (ProgressBar) mView.findViewById(R.id.progress_transfer); 243 mPercentView = (TextView) mView.findViewById(R.id.progress_percent); 244 245 customizeViewContent(); 246 247 // no need update button when activity creating 248 mNeedUpdateButton = false; 249 updateProgressbar(); 250 251 return mView; 252 } 253 254 /** customize the content of view */ customizeViewContent()255 private void customizeViewContent() { 256 String tmp; 257 258 if (mWhichDialog == DIALOG_RECEIVE_ONGOING 259 || mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) { 260 mLine1View = (TextView) mView.findViewById(R.id.line1_view); 261 tmp = getString(R.string.download_line1, mTransInfo.mDeviceName); 262 mLine1View.setText(tmp); 263 mLine2View = (TextView) mView.findViewById(R.id.line2_view); 264 tmp = getString(R.string.download_line2, mTransInfo.mFileName); 265 mLine2View.setText(tmp); 266 mLine3View = (TextView) mView.findViewById(R.id.line3_view); 267 tmp = 268 getString( 269 R.string.download_line3, 270 Formatter.formatFileSize(this, mTransInfo.mTotalBytes)); 271 mLine3View.setText(tmp); 272 mLine5View = (TextView) mView.findViewById(R.id.line5_view); 273 if (mWhichDialog == DIALOG_RECEIVE_ONGOING) { 274 tmp = getString(R.string.download_line5); 275 } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) { 276 tmp = getString(R.string.download_succ_line5); 277 } 278 mLine5View.setText(tmp); 279 } else if (mWhichDialog == DIALOG_SEND_ONGOING 280 || mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) { 281 mLine1View = (TextView) mView.findViewById(R.id.line1_view); 282 tmp = getString(R.string.upload_line1, mTransInfo.mDeviceName); 283 mLine1View.setText(tmp); 284 mLine2View = (TextView) mView.findViewById(R.id.line2_view); 285 tmp = getString(R.string.download_line2, mTransInfo.mFileName); 286 mLine2View.setText(tmp); 287 mLine3View = (TextView) mView.findViewById(R.id.line3_view); 288 tmp = 289 getString( 290 R.string.upload_line3, 291 mTransInfo.mFileType, 292 Formatter.formatFileSize(this, mTransInfo.mTotalBytes)); 293 mLine3View.setText(tmp); 294 mLine5View = (TextView) mView.findViewById(R.id.line5_view); 295 if (mWhichDialog == DIALOG_SEND_ONGOING) { 296 tmp = getString(R.string.upload_line5); 297 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) { 298 tmp = getString(R.string.upload_succ_line5); 299 } 300 mLine5View.setText(tmp); 301 } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) { 302 if (mTransInfo.mStatus == BluetoothShare.STATUS_ERROR_SDCARD_FULL) { 303 mLine1View = (TextView) mView.findViewById(R.id.line1_view); 304 int id = 305 BluetoothOppUtility.deviceHasNoSdCard() 306 ? R.string.bt_sm_2_1_nosdcard 307 : R.string.bt_sm_2_1_default; 308 tmp = getString(id); 309 mLine1View.setText(tmp); 310 mLine2View = (TextView) mView.findViewById(R.id.line2_view); 311 tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName); 312 mLine2View.setText(tmp); 313 mLine3View = (TextView) mView.findViewById(R.id.line3_view); 314 tmp = 315 getString( 316 R.string.bt_sm_2_2, 317 Formatter.formatFileSize(this, mTransInfo.mTotalBytes)); 318 mLine3View.setText(tmp); 319 } else { 320 mLine1View = (TextView) mView.findViewById(R.id.line1_view); 321 tmp = getString(R.string.download_fail_line1); 322 mLine1View.setText(tmp); 323 mLine2View = (TextView) mView.findViewById(R.id.line2_view); 324 tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName); 325 mLine2View.setText(tmp); 326 mLine3View = (TextView) mView.findViewById(R.id.line3_view); 327 tmp = 328 getString( 329 R.string.download_fail_line3, 330 BluetoothOppUtility.getStatusDescription( 331 this, mTransInfo.mStatus, mTransInfo.mDeviceName)); 332 mLine3View.setText(tmp); 333 } 334 mLine5View = (TextView) mView.findViewById(R.id.line5_view); 335 mLine5View.setVisibility(View.GONE); 336 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) { 337 mLine1View = (TextView) mView.findViewById(R.id.line1_view); 338 tmp = getString(R.string.upload_fail_line1, mTransInfo.mDeviceName); 339 mLine1View.setText(tmp); 340 mLine2View = (TextView) mView.findViewById(R.id.line2_view); 341 tmp = getString(R.string.upload_fail_line1_2, mTransInfo.mFileName); 342 mLine2View.setText(tmp); 343 mLine3View = (TextView) mView.findViewById(R.id.line3_view); 344 tmp = 345 getString( 346 R.string.download_fail_line3, 347 BluetoothOppUtility.getStatusDescription( 348 this, mTransInfo.mStatus, mTransInfo.mDeviceName)); 349 mLine3View.setText(tmp); 350 mLine5View = (TextView) mView.findViewById(R.id.line5_view); 351 mLine5View.setVisibility(View.GONE); 352 } 353 354 if (BluetoothShare.isStatusError(mTransInfo.mStatus)) { 355 mProgressTransfer.setVisibility(View.GONE); 356 mPercentView.setVisibility(View.GONE); 357 } 358 } 359 360 @Override onClick(DialogInterface dialog, int which)361 public void onClick(DialogInterface dialog, int which) { 362 switch (which) { 363 case DialogInterface.BUTTON_POSITIVE: 364 if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) { 365 // "Open" - open receive file 366 BluetoothOppUtility.openReceivedFile( 367 this, 368 mTransInfo.mFileName, 369 mTransInfo.mFileType, 370 mTransInfo.mTimeStamp, 371 mUri); 372 373 // make current transfer "hidden" 374 BluetoothOppUtility.updateVisibilityToHidden(this, mUri); 375 376 // clear correspondent notification item 377 getSystemService(NotificationManager.class).cancel(mTransInfo.mID); 378 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) { 379 BluetoothOppUtility.updateVisibilityToHidden(this, mUri); 380 getSystemService(NotificationManager.class).cancel(mTransInfo.mID); 381 } 382 break; 383 384 case DialogInterface.BUTTON_NEGATIVE: 385 if (mWhichDialog == DIALOG_RECEIVE_ONGOING || mWhichDialog == DIALOG_SEND_ONGOING) { 386 // "Stop" button 387 this.getContentResolver().delete(mUri, null, null); 388 389 String msg = ""; 390 if (mWhichDialog == DIALOG_RECEIVE_ONGOING) { 391 msg = getString(R.string.bt_toast_3, mTransInfo.mDeviceName); 392 } else if (mWhichDialog == DIALOG_SEND_ONGOING) { 393 msg = getString(R.string.bt_toast_6, mTransInfo.mDeviceName); 394 } 395 Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 396 397 getSystemService(NotificationManager.class).cancel(mTransInfo.mID); 398 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) { 399 400 BluetoothOppUtility.updateVisibilityToHidden(this, mUri); 401 } 402 break; 403 } 404 finish(); 405 } 406 407 /** Update progress bar per data got from content provider */ updateProgressbar()408 private void updateProgressbar() { 409 mTransInfo = BluetoothOppUtility.queryRecord(this, mUri); 410 if (mTransInfo == null) { 411 Log.e(TAG, "Error: Can not get data from db"); 412 ContentProfileErrorReportUtils.report( 413 BluetoothProfile.OPP, 414 BluetoothProtoEnums.BLUETOOTH_OPP_TRANSFER_ACTIVITY, 415 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 416 1); 417 return; 418 } 419 420 // Set Transfer Max as 100. Percentage calculation would be done in setProgress API 421 mProgressTransfer.setMax(100); 422 423 if (mTransInfo.mTotalBytes != 0) { 424 Log.v( 425 TAG, 426 "mCurrentBytes: " 427 + mTransInfo.mCurrentBytes 428 + " mTotalBytes: " 429 + mTransInfo.mTotalBytes 430 + " (" 431 + (int) ((mTransInfo.mCurrentBytes * 100) / mTransInfo.mTotalBytes) 432 + "%)"); 433 mProgressTransfer.setProgress( 434 (int) ((mTransInfo.mCurrentBytes * 100) / mTransInfo.mTotalBytes)); 435 } else { 436 mProgressTransfer.setProgress(100); 437 } 438 439 mPercentView.setText( 440 BluetoothOppUtility.formatProgressText( 441 mTransInfo.mTotalBytes, mTransInfo.mCurrentBytes)); 442 443 // Handle the case when DIALOG_RECEIVE_ONGOING evolve to 444 // DIALOG_RECEIVE_COMPLETE_SUCCESS/DIALOG_RECEIVE_COMPLETE_FAIL 445 // Handle the case when DIALOG_SEND_ONGOING evolve to 446 // DIALOG_SEND_COMPLETE_SUCCESS/DIALOG_SEND_COMPLETE_FAIL 447 if (!mIsComplete 448 && BluetoothShare.isStatusCompleted(mTransInfo.mStatus) 449 && mNeedUpdateButton) { 450 if (mObserver != null) { 451 getContentResolver().unregisterContentObserver(mObserver); 452 mObserver = null; 453 } 454 displayWhichDialog(); 455 updateButton(); 456 customizeViewContent(); 457 } 458 } 459 460 /** Update button when one transfer goto complete from ongoing */ updateButton()461 private void updateButton() { 462 if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) { 463 changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE); 464 changeButtonText(DialogInterface.BUTTON_POSITIVE, getString(R.string.download_succ_ok)); 465 } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) { 466 changeIconAttribute(android.R.attr.alertDialogIcon); 467 changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE); 468 changeButtonText(DialogInterface.BUTTON_POSITIVE, getString(R.string.download_fail_ok)); 469 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) { 470 changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE); 471 changeButtonText(DialogInterface.BUTTON_POSITIVE, getString(R.string.upload_succ_ok)); 472 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) { 473 changeIconAttribute(android.R.attr.alertDialogIcon); 474 changeButtonText( 475 DialogInterface.BUTTON_NEGATIVE, getString(R.string.upload_fail_cancel)); 476 } 477 } 478 } 479