Add Beam progress indicator.

Now that the Bluetooth stack supports pushing
the count over OPP, we can more reliably indicate
transfer progress.

Also added a notification on the outgoing side
to show progress.

Change-Id: Id0c5e1750a732910d6a5895ab3d318fdfe752a30
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 50cbf63..a4bda3b 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -20,6 +20,7 @@
     <string name="touch">Touch to beam</string>
 
     <string name="beam_progress">Incoming beam...</string>
+    <string name="beam_outgoing">Beaming...</string>
     <string name="beam_complete">Beam complete</string>
     <string name="beam_failed">Beam did not complete</string>
     <string name="beam_canceled">Beam canceled</string>
diff --git a/src/com/android/nfc/handover/HandoverService.java b/src/com/android/nfc/handover/HandoverService.java
index 45521da..181509d 100644
--- a/src/com/android/nfc/handover/HandoverService.java
+++ b/src/com/android/nfc/handover/HandoverService.java
@@ -47,9 +47,16 @@
 
     static final String ACTION_CANCEL_HANDOVER_TRANSFER =
             "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER";
+
     static final String EXTRA_SOURCE_ADDRESS =
             "com.android.nfc.handover.extra.SOURCE_ADDRESS";
 
+    static final String EXTRA_INCOMING =
+            "com.android.nfc.handover.extra.INCOMING";
+
+    static final String ACTION_HANDOVER_STARTED =
+            "android.btopp.intent.action.BT_OPP_HANDOVER_STARTED";
+
     static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
             "android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS";
 
@@ -85,6 +92,9 @@
     static final String EXTRA_BT_OPP_TRANSFER_URI =
             "android.btopp.intent.extra.BT_OPP_TRANSFER_URI";
 
+    public static final String EXTRA_BT_OPP_OBJECT_COUNT =
+            "android.btopp.intent.extra.BT_OPP_OBJECT_COUNT";
+
     // permission needed to be able to receive handover status requests
     static final String HANDOVER_STATUS_PERMISSION =
             "com.android.permission.HANDOVER_STATUS";
@@ -125,6 +135,7 @@
         filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS);
         filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        filter.addAction(ACTION_HANDOVER_STARTED);
         registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, mHandler);
     }
 
@@ -286,7 +297,8 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                        BluetoothAdapter.ERROR);
                 if (state == BluetoothAdapter.STATE_ON) {
                     // If there is a pending headset pairing, start it
                     if (mBluetoothHeadsetHandover != null &&
@@ -294,39 +306,46 @@
                         mBluetoothHeadsetHandover.start();
                     }
 
-                    // Start any pending transfers
+                    // Start any pending file transfers
                     startPendingTransfers();
                 } else if (state == BluetoothAdapter.STATE_OFF) {
                     mBluetoothEnabledByNfc = false;
                     mBluetoothHeadsetConnected = false;
                 }
-            }
-            else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) {
+            } else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) {
                 String sourceAddress = intent.getStringExtra(EXTRA_SOURCE_ADDRESS);
-                HandoverTransfer transfer = findHandoverTransfer(sourceAddress, true);
+                boolean incoming = (intent.getIntExtra(EXTRA_INCOMING, 1)) == 1;
+                HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming);
                 if (transfer != null) {
                     if (DBG) Log.d(TAG, "Cancelling transfer " +
                             Integer.toString(transfer.mTransferId));
                     transfer.cancel();
                 }
             } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS) ||
-                    action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
+                    action.equals(ACTION_BT_OPP_TRANSFER_DONE) ||
+                    action.equals(ACTION_HANDOVER_STARTED)) {
                 int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1);
                 int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1);
+                if (action.equals(ACTION_HANDOVER_STARTED)) {
+                    // This is always for incoming transfers
+                    direction = DIRECTION_BLUETOOTH_INCOMING;
+                }
                 String sourceAddress = intent.getStringExtra(EXTRA_BT_OPP_ADDRESS);
 
-                if (direction == -1 || id == -1 || sourceAddress == null) return;
+                if (direction == -1 || sourceAddress == null) return;
                 boolean incoming = (direction == DIRECTION_BLUETOOTH_INCOMING);
 
                 HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming);
                 if (transfer == null) {
                     // There is no transfer running for this source address; most likely
-                    // the transfer was cancelled. We need to tell BT OPP to stop transferring
-                    // in case this was an incoming transfer
-                    if (DBG) Log.d(TAG, "Didn't find transfer, stopping");
-                    Intent cancelIntent = new Intent("android.btopp.intent.action.STOP_HANDOVER_TRANSFER");
-                    cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id);
-                    sendBroadcast(cancelIntent);
+                    // the transfer was cancelled. We need to tell BT OPP to stop transferring.
+                    if (id != -1) {
+                        if (DBG) Log.d(TAG, "Didn't find transfer, stopping");
+                        Intent cancelIntent = new Intent(
+                                "android.btopp.intent.action.STOP_HANDOVER_TRANSFER");
+                        cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id);
+                        sendBroadcast(cancelIntent);
+                    }
                     return;
                 }
                 if (action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
@@ -346,6 +365,11 @@
                 } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) {
                     float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f);
                     transfer.updateFileProgress(progress);
+                } else if (action.equals(ACTION_HANDOVER_STARTED)) {
+                    int count = intent.getIntExtra(EXTRA_BT_OPP_OBJECT_COUNT, 0);
+                    if (count > 0) {
+                        transfer.setObjectCount(count);
+                    }
                 }
             }
         }
diff --git a/src/com/android/nfc/handover/HandoverTransfer.java b/src/com/android/nfc/handover/HandoverTransfer.java
index 019f0ac..8369678 100644
--- a/src/com/android/nfc/handover/HandoverTransfer.java
+++ b/src/com/android/nfc/handover/HandoverTransfer.java
@@ -89,6 +89,9 @@
 
     // Variables below are only accessed on the main thread
     int mState;
+    int mCurrentCount;
+    int mSuccessCount;
+    int mTotalCount;
     boolean mCalledBack;
     Long mLastUpdate; // Last time an event occurred for this transfer
     float mProgress; // Progress in range [0..1]
@@ -107,6 +110,8 @@
         mRemoteDevice = pendingTransfer.remoteDevice;
         mIncoming = pendingTransfer.incoming;
         mTransferId = pendingTransfer.id;
+        // For incoming transfers, count can be set later
+        mTotalCount = (pendingTransfer.uris != null) ? pendingTransfer.uris.length : 0;
         mLastUpdate = SystemClock.elapsedRealtime();
         mProgress = 0.0f;
         mState = STATE_NEW;
@@ -115,8 +120,10 @@
         mPaths = new ArrayList<String>();
         mMimeTypes = new HashMap<String, String>();
         mMediaUris = new HashMap<String, Uri>();
-        mCancelIntent = buildCancelIntent();
+        mCancelIntent = buildCancelIntent(mIncoming);
         mUrisScanned = 0;
+        mCurrentCount = 0;
+        mSuccessCount = 0;
 
         mHandler = new Handler(Looper.getMainLooper(), this);
         mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
@@ -148,9 +155,11 @@
     public void finishTransfer(boolean success, Uri uri, String mimeType) {
         if (!isRunning()) return; // Ignore when we're no longer running
 
+        mCurrentCount++;
         if (success && uri != null) {
+            mSuccessCount++;
             if (DBG) Log.d(TAG, "Transfer success, uri " + uri + " mimeType " + mimeType);
-            this.mProgress = 1.0f;
+            mProgress = 0.0f;
             if (mimeType == null) {
                 mimeType = BluetoothOppHandover.getMimeTypeForUri(mContext, uri);
             }
@@ -165,8 +174,16 @@
             // Do wait to see if there's another file coming.
         }
         mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
-        mHandler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS);
-        updateStateAndNotification(STATE_W4_NEXT_TRANSFER);
+        if (mCurrentCount == mTotalCount) {
+            if (mIncoming) {
+                processFiles();
+            } else {
+                updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED);
+            }
+        } else {
+            mHandler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS);
+            updateStateAndNotification(STATE_W4_NEXT_TRANSFER);
+        }
     }
 
     public boolean isRunning() {
@@ -177,6 +194,10 @@
         }
     }
 
+    public void setObjectCount(int objectCount) {
+        mTotalCount = objectCount;
+    }
+
     void cancel() {
         if (!isRunning()) return;
 
@@ -190,35 +211,46 @@
     }
 
     void updateNotification() {
-        if (!mIncoming) return; // No notifications for outgoing transfers
-
         Builder notBuilder = new Notification.Builder(mContext);
 
+        String beamString;
+        if (mIncoming) {
+            beamString = mContext.getString(R.string.beam_progress);
+        } else {
+            beamString = mContext.getString(R.string.beam_outgoing);
+        }
         if (mState == STATE_NEW || mState == STATE_IN_PROGRESS ||
                 mState == STATE_W4_NEXT_TRANSFER || mState == STATE_W4_MEDIA_SCANNER) {
             notBuilder.setAutoCancel(false);
             notBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
-            notBuilder.setTicker(mContext.getString(R.string.beam_progress));
-            notBuilder.setContentTitle(mContext.getString(R.string.beam_progress));
+            notBuilder.setTicker(beamString);
+            notBuilder.setContentTitle(beamString);
             notBuilder.addAction(R.drawable.ic_menu_cancel_holo_dark,
                     mContext.getString(R.string.cancel), mCancelIntent);
-            notBuilder.setDeleteIntent(mCancelIntent);
-            // We do have progress indication on a per-file basis, but in a multi-file
-            // transfer we don't know the total progress. So for now, just show an
-            // indeterminate progress bar.
-            notBuilder.setProgress(100, 0, true);
+            float progress = 0;
+            if (mTotalCount > 0) {
+                float progressUnit = 1.0f / mTotalCount;
+                progress = (float) mCurrentCount * progressUnit + mProgress * progressUnit;
+            }
+            if (mTotalCount > 0 && progress > 0) {
+                notBuilder.setProgress(100, (int) (100 * progress), false);
+            } else {
+                notBuilder.setProgress(100, 0, true);
+            }
         } else if (mState == STATE_SUCCESS) {
             notBuilder.setAutoCancel(true);
             notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
             notBuilder.setTicker(mContext.getString(R.string.beam_complete));
             notBuilder.setContentTitle(mContext.getString(R.string.beam_complete));
-            notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view));
 
-            Intent viewIntent = buildViewIntent();
-            PendingIntent contentIntent = PendingIntent.getActivity(
-                    mContext, 0, viewIntent, 0, null);
+            if (mIncoming) {
+                notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view));
+                Intent viewIntent = buildViewIntent();
+                PendingIntent contentIntent = PendingIntent.getActivity(
+                        mContext, mTransferId, viewIntent, 0, null);
 
-            notBuilder.setContentIntent(contentIntent);
+                notBuilder.setContentIntent(contentIntent);
+            }
         } else if (mState == STATE_FAILED) {
             notBuilder.setAutoCancel(false);
             notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
@@ -324,7 +356,7 @@
             if (mIncoming) {
                 processFiles();
             } else {
-                updateStateAndNotification(STATE_SUCCESS);
+                updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED);
             }
             return true;
         } else if (msg.what == MSG_TRANSFER_TIMEOUT) {
@@ -377,10 +409,12 @@
         return viewIntent;
     }
 
-    PendingIntent buildCancelIntent() {
+    PendingIntent buildCancelIntent(boolean incoming) {
         Intent intent = new Intent(HandoverService.ACTION_CANCEL_HANDOVER_TRANSFER);
         intent.putExtra(HandoverService.EXTRA_SOURCE_ADDRESS, mRemoteDevice.getAddress());
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        intent.putExtra(HandoverService.EXTRA_INCOMING, incoming ? 1 : 0);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, mTransferId, intent,
+                PendingIntent.FLAG_ONE_SHOT);
 
         return pi;
     }