Android13 原生设置应用蓝牙配对代码分析研究

Android13 原生设置应用蓝牙配对代码分析研究

一、前言

系统内自己封装的一个应用,在Android11 上连接蓝牙耳机和蓝牙鼠标键盘没啥问题的,
但是移植到Android13 的系统上经常遇到连接蓝牙鼠标键盘是吧的问题。
发现原生Settings 连接未出现这种问题,所有应该是代码处理需要优化。

车载项目蓝牙设置分析研究:
https://blog.csdn.net/liu362732346/article/details/81866575

Android 早期版本 Settings及BluetoothSettings流程分析:
https://blog.csdn.net/weixin_40919230/article/details/80419671

看了下类名称都不一样了,想直接用里面的关键字直接找到蓝牙调用的api好像也不太行!

这里分享一下原生Settings 中蓝牙连接的相关代码分析。

二、Android13 Settings连接蓝牙的主要代码

1、 AndroidManifest,定义Activity 的地方

packages\apps\Settings\AndroidManifest.xml


        <activity android:name=".homepage.SettingsHomepageActivity"
                  android:label="@string/settings_label_launcher"
                  android:theme="@style/Theme.Settings.Home"
                  android:taskAffinity="com.android.settings.root"
                  android:launchMode="singleTask"
                  android:configChanges="keyboard|keyboardHidden">
            <intent-filter android:priority="1">
                <action android:name="android.settings.mtk.SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

        <activity
            android:name=".Settings$ConnectedDeviceDashboardActivity"
            android:label="@string/connected_devices_dashboard_title"
            android:exported="true"
            android:icon="@drawable/ic_homepage_connected_device">
            <intent-filter android:priority="1">
                <action android:name="android.settings.BLUETOOTH_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"/>
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                android:value="@string/menu_key_connected_devices"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                android:value="true" />
        </activity>

        <!-- Alias for launcher activity only, as this belongs to each profile. -->
        <activity-alias android:name="Settings"
                android:label="@string/settings_label_launcher"
                android:taskAffinity="com.android.settings.root"
                android:launchMode="singleTask"
                android:exported="true"
                android:targetActivity=".homepage.SettingsHomepageActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
        </activity-alias>

从上面能看到Settings 的主界面和快速入口,activity-alias 是Acitivity的别名属性,
activity-alias 一般用来设置快速入口和图标,一般app都不怎么用。

所以启动原生Settings 可以用命令:

//快速入口
am start -n com.android.settings/.Settings 

//主界面
am start -n com.android.settings/.homepage.SettingsHomepageActivity

2、 Settings 一个抽象的设置界面

Settings\src\com\android\settings\Settings.java


public static class ConnectedDeviceDashboardActivity extends SettingsActivity {}

里面有上百个空实现的Activity,只复用了 SettingsActivity 的Activity!
这样写的原因是啥???暂时不清楚这个架构!

3、点击"已连接的设备"进入的界面 ConnectedDeviceDashboardFragment

这个TV平台的Settings 代码,没有NFC,这个"已连接的设备"界面就是蓝牙控制连接和显示已保存蓝牙设备的界面。

packages\apps\Settings\src\com\android\settings\connecteddevice\ConnectedDeviceDashboardFragment.java

ConnectedDeviceDashboardFragment 对应的 PreferenceScreen 布局: connected_devices.xml

对应的xml 文件 R.xml.connected_devices :


    <Preference
        android:fragment="com.android.settings.connecteddevice.BluetoothDashboardFragment"
        android:key="bluetooth_switchbar_screen"
        android:title="@string/bluetooth_settings_title"
        android:icon="@*android:drawable/ic_settings_bluetooth"
        android:order="-9"/>


上面的布局默认显示蓝牙界面

Settings\src\com\android\settings\connecteddevice\BluetoothDashboardFragment.java

还有相关的:AdvancedConnectedDeviceDashboardFragment

packages\apps\Settings\res\xml\connected_devices_advanced.xml

布局文件:

//字符串"与新设备配对"的布局
Settings\res\xml\bluetooth_screen.xml

//字符串"连接偏好设置"的布局
Settings\res\xml\connected_devices.xml

上面很多相关跳转还是有点混乱的,可以多加log打印查看。

4、BluetoothPairingDetail 配对列表界面

追一下里面的相关逻辑就能看到调用的具体api了。其实关键就是这个类之后的调用。

//点击"与新设备配对"条目后的配对列表界面
Settings\src\com\android\settings\bluetooth\BluetoothPairingDetail.java

对应布局:
Settings\res\xml\bluetooth_pairing_detail.xml

从上面Java代码和xml 文件中是看不出列表数据的。

(1)BluetoothPairingDetail.java


    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
        LogUtil.debug("btPreference =" + btPreference);//点击每个条目时,是有打印的!自己加的

        disableScanning();
        super.onDevicePreferenceClick(btPreference);//所以重点是看父类的点击事件
    }

BluetoothPairingDetail 的父类是 DeviceListPreferenceFragment。

Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java

    //扫描到的可以连接的蓝牙集合列表
    final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = new HashMap<>();

    //父类中的点击事件
    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
        btPreference.onClicked();//继续追踪 onClicked 方法
    }

(2)BluetoothDevicePreference

Settings\src\com\android\settings\bluetooth\BluetoothDevicePreference.java


    protected final CachedBluetoothDevice mCachedDevice;

    //这里就是原生Settings 中根据蓝牙条目的状态,调用蓝牙的具体api实现。
    void onClicked() {
        Context context = getContext();
        int bondState = mCachedDevice.getBondState();
        LogUtil.debug("bondState = " + bondState);  
        final MetricsFeatureProvider metricsFeatureProvider =
                FeatureFactory.getFactory(context).getMetricsFeatureProvider();

        if (mCachedDevice.isConnected()) { //条目已连接,弹框提示是否断开连接
            metricsFeatureProvider.action(context,
                    SettingsEnums.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
            askDisconnect();
        } else if (bondState == BluetoothDevice.BOND_BONDED) { //已保存的情况,直接进行连接操作
            metricsFeatureProvider.action(context,
                    SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
            mCachedDevice.connect();
        } else if (bondState == BluetoothDevice.BOND_NONE) {
            metricsFeatureProvider.action(context,
                    SettingsEnums.ACTION_SETTINGS_BLUETOOTH_PAIR);
            if (!mCachedDevice.hasHumanReadableName()) {
                metricsFeatureProvider.action(context,
                        SettingsEnums.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
            }
            pair();
        }
    }


    //未连接的设备,配对
    private void pair() {
        if (!mCachedDevice.startPairing()) { //这里看到只是调用了 startPairing 方法!
            Utils.showError(getContext(), mCachedDevice.getName(),
                    R.string.bluetooth_pairing_error_message);
        }
    }

5、BluetoothDeviceDetailsFragment 取消保存和断开连接界面

packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothDeviceDetailsFragment.java

//点击已连接的蓝牙条目,跳转显示"取消保存"和"断开连接"界面
//点击"断开连接"–》变成"连接",进入可连接状态

    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();

        if (mCachedDevice != null) {
            Lifecycle lifecycle = getSettingsLifecycle();
            controllers.add(new BluetoothDetailsHeaderController(context, this, mCachedDevice,
                    lifecycle, mManager));
            //"取消保存","断开连接","连接" 事件都 BluetoothDetailsButtonsController 在里面
            controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice,
                    lifecycle));
            controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
                    mCachedDevice, lifecycle));
            controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
                    lifecycle));
            controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
                    mCachedDevice, lifecycle));
            controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
                    lifecycle));
            controllers.add(new BluetoothDetailsRelatedToolsController(context, this, mCachedDevice,
                    lifecycle));
            controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice,
                    lifecycle));
        }
        return controllers;
    }

(2)BluetoothDetailsButtonsController 事件监听

    //点击"取消保存"触发
    private void onForgetButtonPressed() {
        LogUtil.debug("");
        ForgetDeviceDialogFragment fragment =
                ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
        fragment.show(mFragment.getFragmentManager(), ForgetDeviceDialogFragment.TAG);
    }

    //界面创建的时候,"取消保存"按钮
    @Override
    protected void init(PreferenceScreen screen) {
        mActionButtons = ((ActionButtonsPreference) screen.findPreference(
                getPreferenceKey()))
                .setButton1Text(R.string.forget)
                .setButton1Icon(R.drawable.ic_settings_delete)
                .setButton1OnClickListener((view) -> onForgetButtonPressed())
                .setButton1Enabled(true);
    }



    //界面resume的时候化,更新蓝牙的状态显示:"断开连接"/"连接"按钮,并添加监听事件
    @Override
    protected void refresh() {
        mActionButtons.setButton2Enabled(!mCachedDevice.isBusy());

        boolean previouslyConnected = mIsConnected;
        mIsConnected = mCachedDevice.isConnected();
        if (mIsConnected) {
            if (!mConnectButtonInitialized || !previouslyConnected) {
                mActionButtons
                        .setButton2Text(R.string.bluetooth_device_context_disconnect)
                        .setButton2Icon(R.drawable.ic_settings_close)
                        .setButton2OnClickListener(view -> {
                            mMetricsFeatureProvider.action(mContext,
                                    SettingsEnums.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
                            //断开蓝牙的api
                            mCachedDevice.disconnect();
                        });
                mConnectButtonInitialized = true;
            }
        } else {
            if (!mConnectButtonInitialized || previouslyConnected) {
                mActionButtons
                        .setButton2Text(R.string.bluetooth_device_context_connect)
                        .setButton2Icon(R.drawable.ic_add_24dp)
                        .setButton2OnClickListener(
                                view -> {
                                    mMetricsFeatureProvider.action(mContext,
                                            SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
                                    //连接断开过的蓝牙的api
                                    mCachedDevice.connect();
                                });
                mConnectButtonInitialized = true;
            }
        }
    }


(3)确认忘记设备对话框界面 ForgetDeviceDialogFragment

Settings\src\com\android\settings\bluetooth\ForgetDeviceDialogFragment.java


 private CachedBluetoothDevice mDevice;

    @Override
    public Dialog onCreateDialog(Bundle inState) {
        DialogInterface.OnClickListener onConfirm = (dialog, which) -> {
            //确认忘记蓝牙的api
            mDevice.unpair(); 
            Activity activity = getActivity();
            if (activity != null) {
                activity.finish();
            }
        };
        Context context = getContext();
        mDevice = getDevice(context);

        AlertDialog dialog = new AlertDialog.Builder(context)
                .setPositiveButton(R.string.bluetooth_unpair_dialog_forget_confirm_button,
                        onConfirm)
                .setNegativeButton(android.R.string.cancel, null)
                .create();
        dialog.setTitle(R.string.bluetooth_unpair_dialog_title);
        dialog.setMessage(context.getString(R.string.bluetooth_unpair_dialog_body,
                mDevice.getName()));
        return dialog;
    }


//似乎只执行了这个:
mDevice.unpair();

三、总结原生Settings中对蓝牙连接断开代码调用:



    //获取蓝牙列表
    LocalBluetoothManager mLocalManager = LocalBluetoothManager.getInstance(context, mOnInitCallback);;
    mLocalManager.getCachedDeviceManager().clearNonBondedDevices();//清除扫描列表
    Collection<CachedBluetoothDevice> cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();//获取扫描列表
    //蓝牙单个对象 CachedBluetoothDevice
    protected final CachedBluetoothDevice mCachedDevice;

    //1、未连接的情况,配对+连接
    boolean isParing = mCachedDevice.startPairing();
    //2、已连接的情况,断开连接,进入保存状态
    mCachedDevice.disconnect();
    //3、断开连接,进入保存状态的情况,重新连接
     mCachedDevice.connect();
    //4、连接/保存状态,忘记设备
    mCachedDevice.unpair(); 

LocalBluetoothManager 和 CachedBluetoothDevice 都是系统SettingsLib包中封装的类

LocalBluetoothManager 里面其实是封装了LocalBluetoothAdapter ,最后是调用了原生的 BluetoothAdapter;
CachedBluetoothDevice 其实是对蓝牙基本数据 BluetoothAdapter 的进一步封装。

Android 9 好像是看过TvSettings 的蓝牙连接过程,跟原生Settings 写法好像并不完全相同!

而 LocalBluetoothManager 的api的调用具体是在原生api上做了什么优化,需要大家自己探索研究了!

猜你喜欢

转载自blog.csdn.net/wenzhi20102321/article/details/131648367