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