OSDN Git Service

Enable Bluetooth sharing of downloaded files.
authorJake Hamby <jhamby@google.com>
Fri, 27 Jul 2012 21:38:32 +0000 (14:38 -0700)
committerJake Hamby <jhamby@google.com>
Fri, 14 Sep 2012 23:00:51 +0000 (16:00 -0700)
Change BT OPP to open the InputStreams of files to share in the
BluetoothOppLauncherActivity (or BluetoothOppHandoverReceiver
for NFC shares), while the process has grantUriPermission() on
the URI's to share.

InputStreams are saved inside the existing BluetoothOppSendFileInfo
objects, which no longer include the mDestination field because
this isn't known at the time the SendFileInfo object is now created
(before the user has chosen the destination BT device). These
objects are stored in a static ConcurrentHashMap in BluetoothOppUtility
and are removed when the file is closed (on success or failure).

If the user tries to share thousands of files in one batch, we may
not be able to open InputStreams for all of the files in the batch.
In this case, the open should fail gracefully.

Bug: 6808783
Change-Id: I3f3f86d2dc1a78a837aeb6a888f90b26434ba499

src/com/android/bluetooth/opp/BluetoothOppManager.java
src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
src/com/android/bluetooth/opp/BluetoothOppService.java
src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
src/com/android/bluetooth/opp/BluetoothOppTransfer.java
src/com/android/bluetooth/opp/BluetoothOppUtility.java

index c996690..dd8efe0 100644 (file)
@@ -246,12 +246,15 @@ public class BluetoothOppManager {
         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 @@ public class BluetoothOppManager {
             mMimeTypeOfSendingFiles = mimeType;
             mUrisOfSendingFiles = uris;
             mIsHandoverInitiated = isHandover;
+            for (Uri uri : uris) {
+                BluetoothOppUtility.putSendFileInfo(uri,
+                        BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType));
+            }
             storeApplicationData();
         }
     }
index f234203..dce7fa3 100644 (file)
@@ -305,8 +305,7 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession {
         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 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession {
             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 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession {
                 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 class BluetoothOppObexClientSession implements BluetoothOppObexSession {
         }
     }
 
-    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 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession {
             // 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;
index ec879f0..81c3c92 100644 (file)
 
 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 @@ public class BluetoothOppSendFileInfo {
 
     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 class BluetoothOppSendFileInfo {
 
     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 @@ public class BluetoothOppSendFileInfo {
                     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 @@ public class BluetoothOppSendFileInfo {
                     // 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 @@ public class BluetoothOppSendFileInfo {
         }
         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 @@ public class BluetoothOppSendFileInfo {
                 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);
     }
 }
index 5a30433..9020006 100755 (executable)
@@ -543,9 +543,18 @@ public class BluetoothOppService extends Service {
     }
 
     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 @@ public class BluetoothOppService extends Service {
         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 @@ public class BluetoothOppService extends Service {
         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);
index da57bd2..32f6b3c 100644 (file)
@@ -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 class BluetoothOppShareInfo {
 
     public int mId;
 
-    public String mUri;
+    public Uri mUri;
 
     public String mHint;
 
@@ -66,7 +68,7 @@ public class BluetoothOppShareInfo {
 
     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;
index 44d9bb6..2be679c 100755 (executable)
@@ -315,9 +315,9 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch
                 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);
index df26bd2..7e03281 100644 (file)
@@ -49,8 +49,10 @@ import android.database.Cursor;
 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 @@ public class BluetoothOppUtility {
     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 @@ public class BluetoothOppUtility {
                 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) {
+            }
+        }
+    }
 }