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