Android13 네이티브 설정 어플리케이션 블루투스 페어링 코드 분석 연구

Android13 네이티브 설정 어플리케이션 블루투스 페어링 코드 분석 연구

I. 소개

시스템 자체에 패키징된 어플리케이션으로 Android11에서 블루투스 헤드셋과 블루투스 마우스 및 키보드를 연결하는 것은 문제가 없으나
블루투스 마우스 및 키보드를 연결하기 위해 Android13 시스템으로 포팅할 때 자주 접하게 됩니다.
네이티브 Settings 연결에서는 이 문제가 발생하지 않는 것으로 확인되었으므로 코드 처리를 최적화해야 합니다.

차량용 블루투스 설정 분석 및 연구:
https://blog.csdn.net/liu362732346/article/details/81866575

Android 초기 버전 설정 및 BluetoothSettings 프로세스 분석:
https://blog.csdn.net/weixin_40919230/article/details/80419671

클래스 이름이 다르다고 읽어보니 블루투스에서 호출하는 API를 직접 찾기 위해 그 안에 있는 키워드를 직접적으로 사용하는 것은 좋지 않은 것 같습니다!

여기에서 기본 설정에서 블루투스 연결의 관련 코드 분석을 공유합니다.

2. 블루투스 연결을 위한 Android13 설정의 기본 코드

1. Activity가 정의된 AndroidManifest

패키지\앱\설정\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의 alias 속성입니다.
activity-alias는 일반적으로 빠른 입력과 아이콘을 설정하는 데 사용되며 일반 앱에서는 많이 사용되지 않습니다.

따라서 기본 설정을 시작하려면 다음 명령을 사용할 수 있습니다.

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

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

2. 설정 추상 설정 인터페이스

설정\src\com\안드로이드\설정\Settings.java


public static class ConnectedDeviceDashboardActivity extends SettingsActivity {}

수백 개의 빈 활동이 있으며 SettingsActivity의 활동만 재사용됩니다!
이렇게 쓰는 이유가 뭔가요? ? ? 당분간은 구조를 모른다!

3. "연결된 장치"를 클릭하여 ConnectedDeviceDashboardFragment 인터페이스로 들어갑니다.

TV 플랫폼의 설정 코드에는 NFC가 없습니다. "연결된 장치" 인터페이스는 Bluetooth 제어 연결 및 저장된 Bluetooth 장치 표시를 위한 인터페이스입니다.

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


//"Connection Preferences" Settings\res\xml\connected_devices.xml 문자열의 레이아웃

위의 많은 관련 점프는 여전히 약간 혼란스럽습니다. 더 많은 로그를 인쇄하여 확인할 수 있습니다.

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) 블루투스 장치 기본 설정

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

//연결된 Bluetooth 항목을 클릭하면 "저장 취소" 및 "연결 끊기" 인터페이스로 이동합니다.
//"연결 끊기"를 클릭합니다. – "연결"로 변경하고 연결 가능한 상태로 들어갑니다.

    @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

설정\src\com\안드로이드\설정\블루투스\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();

3. 기본 설정에서 Bluetooth 연결 해제 코드에 대한 호출을 요약합니다.



    //获取蓝牙列表
    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는 실제로 Bluetooth 기본 데이터 BluetoothAdapter의 추가 캡슐화입니다.

Android 9은 TvSettings의 Bluetooth 연결 프로세스를 본 것 같습니다. 이는 원래 설정과 완전히 동일하지 않습니다!

LocalBluetoothManager의 api 호출은 네이티브 api에서 어떤 최적화가 이루어졌는지 직접 탐색하고 공부해야 합니다!

추천

출처blog.csdn.net/wenzhi20102321/article/details/131648367