Merge "Enable Bluetooth sharing of downloaded files." into jb-mr1-dev
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index c996690..dd8efe0 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -246,12 +246,15 @@
         if (V) Log.v(TAG, "Application data stored to SharedPreference! ");
     }
 
-    public void saveSendingFileInfo(String mimeType, String uri, boolean isHandover) {
+    public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover) {
         synchronized (BluetoothOppManager.this) {
             mMultipleFlag = false;
             mMimeTypeOfSendingFile = mimeType;
-            mUriOfSendingFile = uri;
+            mUriOfSendingFile = uriString;
             mIsHandoverInitiated = isHandover;
+            Uri uri = Uri.parse(uriString);
+            BluetoothOppUtility.putSendFileInfo(uri,
+                    BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType));
             storeApplicationData();
         }
     }
@@ -262,6 +265,10 @@
             mMimeTypeOfSendingFiles = mimeType;
             mUrisOfSendingFiles = uris;
             mIsHandoverInitiated = isHandover;
+            for (Uri uri : uris) {
+                BluetoothOppUtility.putSendFileInfo(uri,
+                        BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType));
+            }
             storeApplicationData();
         }
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index f234203..dce7fa3 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -305,8 +305,7 @@
         private BluetoothOppSendFileInfo processShareInfo() {
             if (V) Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId);
 
-            BluetoothOppSendFileInfo fileInfo = BluetoothOppSendFileInfo.generateFileInfo(
-                    mContext1, mInfo.mUri, mInfo.mMimetype, mInfo.mDestination);
+            BluetoothOppSendFileInfo fileInfo = BluetoothOppUtility.getSendFileInfo(mInfo.mUri);
             if (fileInfo.mFileName == null || fileInfo.mLength == 0) {
                 if (V) Log.v(TAG, "BluetoothOppSendFileInfo get invalid file");
                     Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus);
@@ -343,7 +342,7 @@
             request.setHeader(HeaderSet.NAME, fileInfo.mFileName);
             request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);
 
-            applyRemoteDeviceQuirks(request, fileInfo);
+            applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName);
 
             Constants.updateShareStatus(mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING);
 
@@ -500,7 +499,8 @@
                 handleSendException(e.toString());
             } finally {
                 try {
-                    fileInfo.mInputStream.close();
+                    // Close InputStream and remove SendFileInfo from map
+                    BluetoothOppUtility.closeSendFileInfo(mInfo.mUri);
                     if (!error) {
                         responseCode = putOperation.getResponseCode();
                         if (responseCode != -1) {
@@ -566,8 +566,7 @@
         }
     }
 
-    public static void applyRemoteDeviceQuirks(HeaderSet request, BluetoothOppSendFileInfo info) {
-        String address = info.mDestAddr;
+    public static void applyRemoteDeviceQuirks(HeaderSet request, String address, String filename) {
         if (address == null) {
             return;
         }
@@ -576,8 +575,6 @@
             // Rejects filenames with more than one '.'. Rename to '_'.
             // for example: 'a.b.jpg' -> 'a_b.jpg'
             //              'abc.jpg' NOT CHANGED
-            String filename = info.mFileName;
-
             char[] c = filename.toCharArray();
             boolean firstDot = true;
             boolean modified = false;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
index ec879f0..81c3c92 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -32,19 +32,18 @@
 
 package com.android.bluetooth.opp;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-import android.util.Log;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.ParcelFileDescriptor;
 import android.provider.OpenableColumns;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 
 /**
  * This class stores information about a single sending file It will only be
@@ -57,6 +56,10 @@
 
     private static final boolean V = Constants.VERBOSE;
 
+    /** Reusable SendFileInfo for error status. */
+    static final BluetoothOppSendFileInfo SEND_FILE_INFO_ERROR = new BluetoothOppSendFileInfo(
+            null, null, 0, null, BluetoothShare.STATUS_FILE_ERROR);
+
     /** readable media file name */
     public final String mFileName;
 
@@ -72,46 +75,40 @@
 
     public final long mLength;
 
-    public final String mDestAddr;
-
     /** for media file */
     public BluetoothOppSendFileInfo(String fileName, String type, long length,
-            FileInputStream inputStream, int status, String dest) {
+            FileInputStream inputStream, int status) {
         mFileName = fileName;
         mMimetype = type;
         mLength = length;
         mInputStream = inputStream;
         mStatus = status;
-        mDestAddr = dest;
         mData = null;
     }
 
     /** for vCard, or later for vCal, vNote. Not used currently */
-    public BluetoothOppSendFileInfo(String data, String type, long length, int status,
-            String dest) {
+    public BluetoothOppSendFileInfo(String data, String type, long length, int status) {
         mFileName = null;
         mInputStream = null;
         mData = data;
         mMimetype = type;
         mLength = length;
         mStatus = status;
-        mDestAddr = dest;
     }
 
-    public static BluetoothOppSendFileInfo generateFileInfo(Context context, String uri,
-            String type, String dest) {
+    public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri,
+            String type) {
         ContentResolver contentResolver = context.getContentResolver();
-        Uri u = Uri.parse(uri);
-        String scheme = u.getScheme();
+        String scheme = uri.getScheme();
         String fileName = null;
-        String contentType = null;
+        String contentType;
         long length = 0;
         // Support all Uri with "content" scheme
         // This will allow more 3rd party applications to share files via
         // bluetooth
-        if (scheme.equals("content")) {
-            contentType = contentResolver.getType(u);
-            Cursor metadataCursor = contentResolver.query(u, new String[] {
+        if ("content".equals(scheme)) {
+            contentType = contentResolver.getType(uri);
+            Cursor metadataCursor = contentResolver.query(uri, new String[] {
                     OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
             }, null, null, null);
             if (metadataCursor != null) {
@@ -125,25 +122,23 @@
                     metadataCursor.close();
                 }
             }
-        } else if (scheme.equals("file")) {
-            fileName = u.getLastPathSegment();
+        } else if ("file".equals(scheme)) {
+            fileName = uri.getLastPathSegment();
             contentType = type;
-            File f = new File(u.getPath());
+            File f = new File(uri.getPath());
             length = f.length();
         } else {
             // currently don't accept other scheme
-            return new BluetoothOppSendFileInfo(null, null, 0, null,
-                    BluetoothShare.STATUS_FILE_ERROR, dest);
+            return SEND_FILE_INFO_ERROR;
         }
         FileInputStream is = null;
         if (scheme.equals("content")) {
-            AssetFileDescriptor fd = null;
             try {
                 // We've found that content providers don't always have the
                 // right size in _OpenableColumns.SIZE
                 // As a second source of getting the correct file length,
                 // get a file descriptor and get the stat length
-                fd = contentResolver.openAssetFileDescriptor(u, "r");
+                AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
                 long statLength = fd.getLength();
                 if (length != statLength && statLength > 0) {
                     Log.e(TAG, "Content provider length is wrong (" + Long.toString(length) +
@@ -154,7 +149,7 @@
                     // This creates an auto-closing input-stream, so
                     // the file descriptor will be closed whenever the InputStream
                     // is closed.
-                    is = (FileInputStream)fd.createInputStream();
+                    is = fd.createInputStream();
                 } catch (IOException e) {
                     try {
                         fd.close();
@@ -168,10 +163,9 @@
         }
         if (is == null) {
             try {
-                is = (FileInputStream)contentResolver.openInputStream(u);
+                is = (FileInputStream) contentResolver.openInputStream(uri);
             } catch (FileNotFoundException e) {
-                return new BluetoothOppSendFileInfo(null, null, 0, null,
-                        BluetoothShare.STATUS_FILE_ERROR, dest);
+                return SEND_FILE_INFO_ERROR;
             }
         }
         // If we can not get file length from content provider, we can try to
@@ -182,11 +176,10 @@
                 if (V) Log.v(TAG, "file length is " + length);
             } catch (IOException e) {
                 Log.e(TAG, "Read stream exception: ", e);
-                return new BluetoothOppSendFileInfo(null, null, 0, null,
-                        BluetoothShare.STATUS_FILE_ERROR, dest);
+                return SEND_FILE_INFO_ERROR;
             }
         }
 
-        return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0, dest);
+        return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0);
     }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 5a30433..9020006 100755
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -543,9 +543,18 @@
     }
 
     private void insertShare(Cursor cursor, int arrayPos) {
+        String uriString = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
+        Uri uri;
+        if (uriString != null) {
+            uri = Uri.parse(uriString);
+            Log.d(TAG, "insertShare parsed URI: " + uri);
+        } else {
+            uri = null;
+            Log.e(TAG, "insertShare found null URI at cursor!");
+        }
         BluetoothOppShareInfo info = new BluetoothOppShareInfo(
                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)),
-                cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI)),
+                uri,
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
@@ -597,23 +606,12 @@
         if (info.isReadyToStart()) {
             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                 /* check if the file exists */
-                InputStream i;
-                try {
-                    i = getContentResolver().openInputStream(Uri.parse(info.mUri));
-                } catch (FileNotFoundException e) {
+                BluetoothOppSendFileInfo sendFileInfo = BluetoothOppUtility.getSendFileInfo(
+                        info.mUri);
+                if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
                     Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
                     Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
-                    return;
-                } catch (SecurityException e) {
-                    Log.e(TAG, "Exception:" + e.toString() + " for OUTBOUND info " + info.mId);
-                    Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
-                    return;
-                }
-
-                try {
-                    i.close();
-                } catch (IOException ex) {
-                    Log.e(TAG, "IO error when close file for OUTBOUND info " + info.mId);
+                    BluetoothOppUtility.closeSendFileInfo(info.mUri);
                     return;
                 }
             }
@@ -678,7 +676,12 @@
         int statusColumn = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
 
         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
-        info.mUri = stringFromCursor(info.mUri, cursor, BluetoothShare.URI);
+        if (info.mUri != null) {
+            info.mUri = Uri.parse(stringFromCursor(info.mUri.toString(), cursor,
+                    BluetoothShare.URI));
+        } else {
+            Log.d(TAG, "updateShare() called for ID " + info.mId + " with null URI");
+        }
         info.mHint = stringFromCursor(info.mHint, cursor, BluetoothShare.FILENAME_HINT);
         info.mFilename = stringFromCursor(info.mFilename, cursor, BluetoothShare._DATA);
         info.mMimetype = stringFromCursor(info.mMimetype, cursor, BluetoothShare.MIMETYPE);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java b/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
index da57bd2..32f6b3c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
@@ -32,6 +32,8 @@
 
 package com.android.bluetooth.opp;
 
+import android.net.Uri;
+
 /**
  * This class stores information about a single OBEX share, e.g. one object
  * send/receive to a destination address.
@@ -40,7 +42,7 @@
 
     public int mId;
 
-    public String mUri;
+    public Uri mUri;
 
     public String mHint;
 
@@ -66,7 +68,7 @@
 
     public boolean mMediaScanned;
 
-    public BluetoothOppShareInfo(int id, String uri, String hint, String filename, String mimetype,
+    public BluetoothOppShareInfo(int id, Uri uri, String hint, String filename, String mimetype,
             int direction, String destination, int visibility, int confirm, int status,
             int totalBytes, int currentBytes, int timestamp, boolean mediaScanned) {
         mId = id;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 44d9bb6..2be679c 100755
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -315,9 +315,9 @@
                 updateValues.put(BluetoothShare.STATUS, info.mStatus);
                 /* Update un-processed outbound transfer to show some info */
                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-                    BluetoothOppSendFileInfo fileInfo = null;
-                    fileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, info.mUri,
-                            info.mMimetype, info.mDestination);
+                    BluetoothOppSendFileInfo fileInfo
+                            = BluetoothOppUtility.getSendFileInfo(info.mUri);
+                    BluetoothOppUtility.closeSendFileInfo(info.mUri);
                     if (fileInfo.mFileName != null) {
                         updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
                         updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index df26bd2..7e03281 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -49,8 +49,10 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This class has some utilities for Opp application;
@@ -60,6 +62,9 @@
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
 
+    private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap
+            = new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
+
     public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
@@ -303,4 +308,25 @@
                 transInfo.mDeviceName);
     }
 
+    static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
+        if (D) Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo);
+        sSendFileMap.put(uri, sendFileInfo);
+    }
+
+    static BluetoothOppSendFileInfo getSendFileInfo(Uri uri) {
+        if (D) Log.d(TAG, "getSendFileInfo: uri=" + uri);
+        BluetoothOppSendFileInfo info = sSendFileMap.get(uri);
+        return (info != null) ? info : BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR;
+    }
+
+    static void closeSendFileInfo(Uri uri) {
+        if (D) Log.d(TAG, "closeSendFileInfo: uri=" + uri);
+        BluetoothOppSendFileInfo info = sSendFileMap.remove(uri);
+        if (info != null) {
+            try {
+                info.mInputStream.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
 }