Android 蓝牙无法发送或接收某些类型文件

Android 蓝牙应用使得用户能够在蓝牙设备之间进行文件传输。用户可以通过蓝牙连接两台设备,并在它们之间传输文件,如照片、音乐、视频等。这对于用户来说是非常便利的,无需使用数据线或互联网连接,可以直接在附近的设备之间进行文件共享。

文章目录

  • [1. 蓝牙无法发送某些类型文件](#1. 蓝牙无法发送某些类型文件)
  • [2. 蓝牙无法接收某些类型文件](#2. 蓝牙无法接收某些类型文件)
  • [3. 蓝牙权限问题](#3. 蓝牙权限问题)

从 Andorid 13 后蓝牙模块位置从 packages/apps/Bluetooth/变成了 packages/modules/Bluetooth

bash 复制代码
server@dev-fj-srv:/work/AndrodU/packages/modules/Bluetooth/android/app$ tree -L 1
.
|-- Android.bp
|-- AndroidManifest.xml
|-- OWNERS
|-- app.iml
|-- certs
|-- jni
|-- lib
|-- proguard.flags
|-- res
|-- services
|-- src
|-- tests
`-- tools

8 directories, 5 files

1. 蓝牙无法发送某些类型文件

原生蓝牙的ACTION_SEND_MULTIPLEACTION_SEND二种action分享类型做了限制,所以会导致部分类型的文件无法通过蓝牙分享,为了解除这种限制,可以将数据类型设置成*/*,即<data android:mimeType="*/*" />

packages/modules/Bluetooth/android/app/AndroidManifest.xml

xml 复制代码
<activity android:name="com.android.bluetooth.opp.BluetoothOppLauncherActivity"
     android:process="@string/process"
     android:theme="@style/opp_launcher_activity"
     android:label="@string/bt_share_picker_label"
     android:enabled="false"
     android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <!-- @{ support sending all types of files.
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
        <data android:mimeType="audio/*"/>
        <data android:mimeType="text/x-vcard"/>
        <data android:mimeType="text/x-vcalendar"/>
        <data android:mimeType="text/calendar"/>
        <data android:mimeType="text/plain"/>
        <data android:mimeType="text/html"/>
        <data android:mimeType="text/xml"/>
        <data android:mimeType="application/zip"/>
        <data android:mimeType="application/vnd.ms-excel"/>
        <data android:mimeType="application/msword"/>
        <data android:mimeType="application/vnd.ms-powerpoint"/>
        <data android:mimeType="application/pdf"/>
        <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/>
        <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document"/>
        <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation"/>
        <data android:mimeType="application/x-hwp"/>
        -->
        <data android:mimeType="*/*" />
        <!-- @} -->
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <!-- @{ support sending all types of files.
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
        <data android:mimeType="x-mixmedia/*"/>
        <data android:mimeType="text/x-vcard"/>
        -->
        <data android:mimeType="*/*" />
        <!-- @} -->
    </intent-filter>
    <intent-filter>
        <action android:name="android.btopp.intent.action.OPEN"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="vnd.android.cursor.item/vnd.android.btopp"/>
    </intent-filter>
</activity>

2. 蓝牙无法接收某些类型文件

log 报错分析

bash 复制代码
04-20 19:27:10.928 23480 12218 D BtOppObexClient: Start!
04-20 19:27:11.031 23480 12224 D BtOppObexClient: Create ClientSession with transport com.android.bluetooth.BluetoothObexTransport@c894e88
04-20 19:27:11.227 23480 12224 D BtOppObexClient: OBEX session created
04-20 19:27:11.808 23480 12224 I BtOppObexClient: Remote reject, Response code is 207
04-20 19:27:11.810 23480 12224 I BtOppObexClient: Remote reject file type application/vnd.android.package-archive
04-20 19:27:11.810 23480 12224 I BtOppObexClient: Response error code is 207
04-20 19:27:11.824 23480 12224 D BtOppObexClient: Client thread waiting for next share, sleep for 500
04-20 19:27:11.825 23480 12218 D BtOppObexClient: Stop!
04-20 19:27:11.917 23480 12224 D BtOppObexClient: OBEX session disconnected
04-20 19:27:12.932 23480 12231 D BtOppObexClient: Stop!

BtOppObexClient: Remote reject file type application/vnd.android.package-archive 根据log信息找到代码位置:src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java

java 复制代码
@VisibleForTesting
int sendFile(BluetoothOppSendFileInfo fileInfo) {
...
		} else if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) {
		    Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype);
		    status = BluetoothShare.STATUS_NOT_ACCEPTABLE;
		} 
...
}

src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java 中的 public int onPut(Operation op)

java 复制代码
// Reject anything outside the "acceptlist" plus unspecified MIME Types.
if (mimeType == null || (!isAcceptlisted && !Constants.mimeTypeMatches(mimeType,
        Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))) {
    if (D) {
        Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
    }
    return ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
}

此处定义了常量Constants.ACCEPTABLE_SHARE_INBOUND_TYPES,来过滤可接收的文件类型的mimeType

java 复制代码
/**
 * The MIME type(s) of we could accept from other device.
 * This is in essence a "acceptlist" of acceptable types.
 * Today, restricted to images, audio, video and certain text types.
 */
static final String[] ACCEPTABLE_SHARE_INBOUND_TYPES = new String[]{
        "image/*",
        "video/*",
        "audio/*",
        "text/x-vcard",
        "text/x-vcalendar",
        "text/calendar",
        "text/plain",
        "text/html",
        "text/xml",
        "application/epub+zip",
        "application/zip",
        "application/vnd.ms-excel",
        "application/msword",
        "application/vnd.ms-powerpoint",
        "application/pdf",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
        "application/x-hwp",
};

因此,如果需要接收所有文件类型,可以修改此ACCEPTABLE_SHARE_INBOUND_TYPES

java 复制代码
static final String[] ACCEPTABLE_SHARE_INBOUND_TYPES = new String[]{
	"*/*"
};

3. 蓝牙权限问题

部分应用的图片无法通过蓝牙分享,蓝牙通知显示文件传输失败:未知文件,无法正确处理请求。log输出报错如下:

bash 复制代码
12-02 02:12:22.290  5046  6302 E DatabaseUtils: Writing exception to parcel
12-02 02:12:22.290  5046  6302 E DatabaseUtils: java.lang.SecurityException: com.android.bluetooth has no access to content://media/external/images/media/1000000037
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.enforceCallingPermissionInternal(MediaProvider.java:10014)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.enforceCallingPermission(MediaProvider.java:9911)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.checkAccess(MediaProvider.java:10035)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.openFileAndEnforcePathPermissionsHelper(MediaProvider.java:8294)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.openFileCommon(MediaProvider.java:7901)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.openTypedAssetFileCommon(MediaProvider.java:7969)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.openTypedAssetFile(MediaProvider.java:7913)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:562)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:327)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at android.os.Binder.execTransactInternal(Binder.java:1285)
12-02 02:12:22.290  5046  6302 E DatabaseUtils: 	at android.os.Binder.execTransact(Binder.java:1244)

log表明蓝牙无法获取此媒体文件,缺少权限,由于Android 13后新增了媒体文件的细分权限,蓝牙的AndroidManifest.xml中没有添加这些细分权限,遇到的images文件出现的此问题,添加READ_MEDIA_IMAGES权限后测试成功。

xml 复制代码
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

STATUS_OBEX_DATA_ERROR:由于在 OBEX 级别接收或处理数据时出现错误,因此无法完成此传输。

java 复制代码
/**
 * Get status description according to status code.
 */
public static String getStatusDescription(Context context, int statusCode, String deviceName) {
    String ret;
    if (statusCode == BluetoothShare.STATUS_PENDING) {
        ret = context.getString(R.string.status_pending);
    } else if (statusCode == BluetoothShare.STATUS_RUNNING) {
        ret = context.getString(R.string.status_running);
    } else if (statusCode == BluetoothShare.STATUS_SUCCESS) {
        ret = context.getString(R.string.status_success);
    } else if (statusCode == BluetoothShare.STATUS_NOT_ACCEPTABLE) {
        ret = context.getString(R.string.status_not_accept);
    } else if (statusCode == BluetoothShare.STATUS_FORBIDDEN) {
        ret = context.getString(R.string.status_forbidden);
    } else if (statusCode == BluetoothShare.STATUS_CANCELED) {
        ret = context.getString(R.string.status_canceled);
    } else if (statusCode == BluetoothShare.STATUS_FILE_ERROR) {
        ret = context.getString(R.string.status_file_error);
    } else if (statusCode == BluetoothShare.STATUS_ERROR_NO_SDCARD) {
        int id = deviceHasNoSdCard()
                ? R.string.status_no_sd_card_nosdcard
                : R.string.status_no_sd_card_default;
        ret = context.getString(id);
    } else if (statusCode == BluetoothShare.STATUS_CONNECTION_ERROR) {
        ret = context.getString(R.string.status_connection_error);
    } else if (statusCode == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
        int id = deviceHasNoSdCard() ? R.string.bt_sm_2_1_nosdcard : R.string.bt_sm_2_1_default;
        ret = context.getString(id);
    } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST) || (statusCode
            == BluetoothShare.STATUS_LENGTH_REQUIRED) || (statusCode
            == BluetoothShare.STATUS_PRECONDITION_FAILED) || (statusCode
            == BluetoothShare.STATUS_UNHANDLED_OBEX_CODE) || (statusCode
            == BluetoothShare.STATUS_OBEX_DATA_ERROR)) {
        ret = context.getString(R.string.status_protocol_error);
    } else {
        ret = context.getString(R.string.status_unknown_error);
    }
    return ret;
}

无法查询到传输的文件信息,所以显示为未知文件。

java 复制代码
if (info.mFileName == null) {
    info.mFileName = context.getString(R.string.unknown_file);
}
相关推荐
雨白3 分钟前
详解三种常见布局:LinearLayout、RelativeLayout及FrameLayout
android
墨狂之逸才7 分钟前
如何选择合适的abiFilters
android·android studio
宾有为3 小时前
【Android】如何抓取 Android 设备的 UDP/TCP 数据包?
android·tcp/ip·udp·wireshark·抓包·tcp抓包·udp抓包
_一条咸鱼_3 小时前
Android Runtime JNI环境构建与注册过程原理(15)
android·面试·android jetpack
花花鱼4 小时前
Canvas: trying to draw too large(256032000bytes) bitmap.
android
移动开发者1号4 小时前
Fragment懒加载优化方案总结
android·kotlin
夕泠爱吃糖4 小时前
MySQL事务
android·数据库·mysql
移动开发者1号4 小时前
Android Activity启动模式面试题
android·kotlin
robch4 小时前
mysql prepare statement
android·数据库·mysql
alexhilton5 小时前
Jetpack Compose 中ViewModel的最佳实践
android·kotlin·android jetpack