Android Beam json file
Any ideas why?
Android Beam uses the file extension of the first file in the Beam Uri
s array to look up a corresponding MIME type in an internal map, which is then sent with the Intent
that initiates the file transfer via Bluetooth Object Push Profile (OPP).
If the file extension or a matching MIME type is not found, the Intent
's MIME type is set to null
, and the Bluetooth OPP file transfer isn't initiated at all.
WORKAROUND
When sending a file with an extension that is not listed in MimeUtils
, use a two-element Beam Uri
s array:
uris[0]
: a dummy text file with a .txt
extension, (to be deleted later)uris[1]
: the file (with the unrecognized extension) which you want to transfer
In your specific case:
adapter.setBeamPushUris( new Uri[] { dummyTxtFileUri, data.getData() }, this);
Android Beam will send an intent to Bluetooth with a MIME type of text/plain
, along with the Uri
s for both files, and the Bluetooth OPP file transfer will occur normally. Note that when beaming multiple files at once, the receiving device will store the files in a subdirectory in beam/
, usually named beam-YYYY-MM-DD/
.
BACKGROUND
I compared the logs on the sending device between sending a file with a .json
extension, and a copy of that file with a .txt
extension. The first notable difference is here:
log: beaming test.json
03-02 13:19:34.665: D/BluetoothOppHandover(32332): Handing off outging transfer to BT
log: beaming test.txt
03-02 15:32:19.437: D/BluetoothOppHandover(3268): Handing off outging transfer to BT03-02 15:32:19.445: D/BluetoothOppUtility(3309): putSendFileInfo: uri=file:///storage/emulated/0/Download/test.txt@2cb672fa sendFileInfo=com.android.bluetooth.opp.BluetoothOppSendFileInfo@2cb672fa
Searching AOSP for "Handing off outging transfer to BT":
platform/packages/apps/Nfc/src/com/android/nfc/handover/BluetoothOppHandover.java
void sendIntent() { Intent intent = new Intent(); intent.setPackage("com.android.bluetooth"); String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris[0]); intent.setType(mimeType); // ... if (DBG) Log.d(TAG, "Handing off outging transfer to BT"); mContext.sendBroadcast(intent); complete();}
Before moving on, note that MIME type for only the first Uri
in the array is sent in the Intent
. Following MimeTypeUtil.getMimeTypeForUri()
:
platform/packages/apps/Nfc/src/com/android/nfc/handover/MimeTypeUtil.java
public static String getMimeTypeForUri(Context context, Uri uri) { // ... String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath()).toLowerCase(); if (extension != null) { return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); } else { return null; } // ...
So if it doesn't identify an extension, it returns null
as the MIME type. Following MimeTypeMap.getSingleton().getMimeTypeFromExtension()
...
frameworks/base/core/java/android/webkit/MimeTypeMap.java
public String getMimeTypeFromExtension(String extension) { return MimeUtils.guessMimeTypeFromExtension(extension); }
platform/libcore/luni/src/main/java/libcore/net/MimeUtils.java
public final class MimeUtils { private static final Map<String, String> mimeTypeToExtensionMap = new HashMap<String, String>(); private static final Map<String, String> extensionToMimeTypeMap = new HashMap<String, String>(); // ... public static String guessMimeTypeFromExtension(String extension) { if (extension == null || extension.isEmpty()) { return null; } return extensionToMimeTypeMap.get(extension); }
Before moving on, note that this MimeUtils
class contains the list of MIME types that are recognized by Android. It's a good reference.
We've reached the end of the stack with extensionToMimeTypeMap.get()
:
platform/libcore/luni/src/main/java/java/util/HashMap.java
/** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ public V get(Object key) {
So if no match is found, the MIME type is ultimately returned as null
. A bit more digging shows where this matters:
platform/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@Override public void onReceive(Context context, Intent intent) { // ... if (action.equals(Constants.ACTION_HANDOVER_SEND)) { String type = intent.getType(); Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM); if (stream != null && type != null) { // Save type/stream, will be used when adding transfer // session to DB. BluetoothOppManager.getInstance(context).saveSendingFileInfo(type, stream.toString(), true); } else { if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); } // ... // we already know where to send to BluetoothOppManager.getInstance(context).startTransfer(device);
Since there is a null check on the MIME type before saving the file info and starting the transfer, the Bluetooth OPP file transfer never initiates. Note that the other two conditional blocks return
when there is a null
, so it seems the fact that this one is missing a return
after the Log.d()
call may be a bug.