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_MULTIPLE和ACTION_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);
}
相关推荐
三少爷的鞋6 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)9 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码11 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程12 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓16 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer