Android Beam json file Android Beam json file json json

Android Beam json file


Any ideas why?

Android Beam uses the file extension of the first file in the Beam Uris 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 Uris 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 Uris 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.