Android Bluetooth 蓝牙开发/蓝牙协议 小结

发布时间 2024-01-13 09:19:40作者: 僵小七
蓝牙术语

蓝牙

蓝牙术语:
HFP(Hands-free Profile)耳机模式:
让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。

HSP(Handset Profile)耳机模式
用于支持蓝牙耳机与移动电话之间使用

蓝牙电话广播:
BluetoothHeadsetClient.ACTION_CALL_CHANGED
public static final String ACTION_CALL_CHANGED = "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";

BluetoothHeadsetClientCall callStatus = intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
        CALL_STATE_ACTIVE = 0; talking
        CALL_STATE_DIALING = 2; CALL_STATE_ALERTING = 3; out dial
        CALL_STATE_WAITING = 5; waiting
        CALL_STATE_INCOMING = 4; incoming
        CALL_STATE_TERMINATED = 7; hangup

蓝牙电话  手机和耳机音频切换广播:
BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED
public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                BluetoothProfile.STATE_DISCONNECTED);
newState :2 bt
newState :0 phone

[demo](https://blog.csdn.net/weixin_45534242/article/details/124899251)
BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED

 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_AUDIO_STATE_CHANGED =
            "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";

    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
            "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";


获取蓝牙 Pbap 协议栈的支持

蓝牙开发相关协议
Android 蓝牙开发——PBAP协议(十)
BluetoothPbapClient

为了方便开发,Android 提供了若干个蓝牙功能的协议栈,我们称之为 profile 。车机蓝牙开发我们需要用到的 profile 主要有:
Profile	     Description
A2DP Sink	 音频相关
HFP Client	 电话相关
Avrcp Controller	 音频控制相关
Pbap Client	        通讯录、通话记录相关

Android 蓝牙 app开发 函数回调
//\frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth
public class TextApiActivity extends Activity {
    private static final String TAG = "TextApiActivity";

    private LocalBluetoothManager mLocalBluetoothManager;
    private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
    private BluetoothEventManager mBluetoothEventManager;
    private LocalBluetoothProfileManager mBluetoothProfileManager;


    private LocalBluetoothAdapter mLocalBluetoothAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_api);
        mLocalBluetoothManager = getLocalBtManager(TextApiActivity.this);

        mCachedBluetoothDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
        mBluetoothEventManager = mLocalBluetoothManager.getEventManager();
        mBluetoothProfileManager = mLocalBluetoothManager.getProfileManager();

        mBluetoothEventManager.registerCallback(mBluetoothCallback);
        mBluetoothProfileManager.addServiceListener(mServiceListener);


        mLocalBluetoothAdapter = mLocalBluetoothManager.getBluetoothAdapter();

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mLocalBluetoothAdapter != null) {
                    mLocalBluetoothAdapter.startScanning(true);
                }
            }
        });
    }


    public static LocalBluetoothManager getLocalBtManager(Context context) {
        return LocalBluetoothManager.getInstance(context, mOnInitCallback);
    }

    private static final LocalBluetoothManager.BluetoothManagerCallback mOnInitCallback =
            new LocalBluetoothManager.BluetoothManagerCallback() {
                @Override
                public void onBluetoothManagerInitialized(Context appContext, LocalBluetoothManager bluetoothManager) {
                    BluetoothUtils.setErrorListener(mErrorListener);
                }
            };


    //监听协议(Profile)相关接口是否可用
    LocalBluetoothProfileManager.ServiceListener mServiceListener = new LocalBluetoothProfileManager.ServiceListener() {
        @Override
        public void onServiceConnected() {
            //Bluetooth Service bind success 可以调用各Profile的接口
        }

        @Override
        public void onServiceDisconnected() {
            //Bluetooth Service unbind success

        }
    };


    BluetoothCallback mBluetoothCallback = new BluetoothCallback() {
        @Override
        public void onBluetoothStateChanged(int bluetoothState) {
            //蓝牙开关状态变化
            Log.d(TAG, "onBluetoothStateChanged:" + bluetoothState);
            BluetoothCallback.super.onBluetoothStateChanged(bluetoothState);
        }

        @Override
        public void onScanningStateChanged(boolean started) {
            //搜索状态变化
            Log.d(TAG, "onScanningStateChanged:" + started);
            BluetoothCallback.super.onScanningStateChanged(started);
        }

        @Override
        public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
            //搜索到新设备
            Log.d(TAG, "onDeviceAdded:" + cachedDevice.getName());
            BluetoothCallback.super.onDeviceAdded(cachedDevice);
        }

        @Override
        public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
            //设备被移除
            Log.d(TAG, "onDeviceDeleted:" + cachedDevice.getName());
            BluetoothCallback.super.onDeviceDeleted(cachedDevice);
        }

        @Override
        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
            //设备配对状态变化
            Log.d(TAG, "onDeviceBondStateChanged:" + cachedDevice.getName() + "-----》" + bondState);
            BluetoothCallback.super.onDeviceBondStateChanged(cachedDevice, bondState);
        }

        @Override
        public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
            //设备连接状态变化
            Log.d(TAG, "onConnectionStateChanged:" + cachedDevice.getName() + "-----》" + state);
            BluetoothCallback.super.onConnectionStateChanged(cachedDevice, state);
        }

        @Override
        public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
            //活动设备变化
            Log.d(TAG, "onConnectionStateChanged:" + activeDevice.getName() + "bluetoothProfile-----》" + bluetoothProfile);
            BluetoothCallback.super.onActiveDeviceChanged(activeDevice, bluetoothProfile);
        }

        @Override
        public void onAudioModeChanged() {
            BluetoothCallback.super.onAudioModeChanged();
        }

        @Override
        public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, int bluetoothProfile) {
            //协议连接状态变化
            BluetoothCallback.super.onProfileConnectionStateChanged(cachedDevice, state, bluetoothProfile);
        }

        @Override
        public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
            //ACL连接状态变化
            BluetoothCallback.super.onAclConnectionStateChanged(cachedDevice, state);
        }
    };

    private static final ErrorListener mErrorListener = new ErrorListener() {
        @Override
        public void onShowError(Context context, String name, int messageResId) {
            showError(context, name, messageResId);
        }
    };

    static void showError(Context context, String name, int messageResId) {
        showError(context, name, messageResId, getLocalBtManager(context));
    }

    private static void showError(Context context, String name, int messageResId, LocalBluetoothManager manager) {
        String message = context.getString(messageResId, name);
        if (manager.isForegroundActivity()) {
            try {
                Log.d(TAG, "show ErrorDialogFragment, message is " + message);

            } catch (Exception e) {
                Log.e(TAG, "Cannot show error dialog.", e);
            }
        } else {
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
        }
    }
}

Android 蓝牙 app开发 电话范例 -->反射

demo

那么Android手机设备通过蓝牙连接上汽车中控系统后,如何实现打电话功能的?有两种方式实现
1.通过硬件厂商提供的SDK来实现,这部分直接调用提供的API即可,实现简单
2.通过标准的Android 蓝牙协议实现,需要自己来实现,网上这方面的文章也不是很多

通过标准的Android蓝牙如何实现打电话的?
android.bluetooth.BluetoothHeadsetClient是一个系统隐藏类,实现蓝牙电话就是通过这个类完成。
不能直接调用,就只能反射调用了:
public class BluetoothManager {
    private final static String DIAL_CLASS_NAME = "android.bluetooth.BluetoothHeadsetClient";  //拨打电话类名
    private final static String DIAL_METHOD_NAME = "dial";  //拨打电话方法名
    private final static String BLUE_TOOTH_PROFILE_FIELD = "HEADSET_CLIENT";  //蓝牙适配器远程设备类型

    private static BluetoothManager instance;
    private OnCallResultListener onCallResultListener;

    public interface OnCallResultListener {
        //蓝牙未打开
        void onBluetoothIsClosed();

        //蓝牙未连接
        void onDeviceIsEmpty();

        //无效的电话号码
        void onPhoneIsInValid();

        void onError(String errorMsg);
    }

    public static BluetoothManager getInstance() {
        if (instance == null) {
            instance = new BluetoothManager();
        }
        return instance;
    }

    //拨打电话的实际调用
    public void dial(final String number, final OnCallResultListener onCallResultListener) {
        this.onCallResultListener = onCallResultListener;
        if (StringUtils.isEmpty(number)) {
            if (onCallResultListener != null) {
                onCallResultListener.onPhoneIsInValid();
            }
        }
        getConnectStatus(new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                List<BluetoothDevice> mDevices = proxy.getConnectedDevices();
                if (!Utils.isListEmpty(mDevices)) {
                    for (BluetoothDevice device : mDevices) {
                        dial(proxy, device, number);
                        break;
                    }
                } else {
                    if (onCallResultListener != null) {
                        onCallResultListener.onDeviceIsEmpty();
                    }
                }
            }

            @Override
            public void onServiceDisconnected(int profile) {
                if (onCallResultListener != null) {
                    onCallResultListener.onDeviceIsEmpty();
                }
            }
        });
    }

    //获取蓝牙的连接状态
    private void getConnectStatus(BluetoothProfile.ServiceListener serviceListener) {
        final int bluetooth_Profile_HEADSET_CLIENT;
        try {
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
            if (!adapter.isEnabled()) {
                if (onCallResultListener != null) {
                    onCallResultListener.onBluetoothIsClosed();
                    return;
                }
            }

            bluetooth_Profile_HEADSET_CLIENT = (int) BluetoothProfile.class.getField(BLUE_TOOTH_PROFILE_FIELD).get(null);
            //获取到蓝牙电话的连接状态
            int isConnected = adapter.getProfileConnectionState(bluetooth_Profile_HEADSET_CLIENT);
            if (isConnected == BluetoothProfile.STATE_DISCONNECTED) {
                if (onCallResultListener != null) {
                    onCallResultListener.onDeviceIsEmpty();
                    return;
                }
            }
            adapter.getProfileProxy(CommonApp.getCommonApp().getApplication(), serviceListener, bluetooth_Profile_HEADSET_CLIENT);

        } catch (Exception e) {
            if (onCallResultListener != null) {
                onCallResultListener.onError(e.getMessage());
            }
        }
    }

    /***
     * 拨号
     * @param proxy
     * @param bluetoothDevice
     * @param number
     */
    private void dial(BluetoothProfile proxy, BluetoothDevice bluetoothDevice, String number) {
        try {
            if (proxy == null || bluetoothDevice == null || (StringUtils.isEmpty(number))) {
                return;
            }
            Class BluetoothHeadsetClient = Class.forName(DIAL_CLASS_NAME);
            Method methodMain = BluetoothHeadsetClient.getMethod(DIAL_METHOD_NAME, BluetoothDevice.class, String.class);
            if (methodMain != null) {
                //蓝牙电话调用的具体执行
                methodMain.invoke(proxy, bluetoothDevice, number);
            }
        } catch (Exception e) {
            if (onCallResultListener != null) {
                onCallResultListener.onError(e.getMessage());
            }
        }
    }
}

//如果有系统源码,采用定制化的系统,直接引入out下面的framework.jar
private void createBtHeadSetClient() {
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter != null) {
            bluetoothAdapter.getProfileProxy(getApplicationContext(), new BluetoothProfile.ServiceListener() {
                @Override
                public void onServiceConnected(int profile, BluetoothProfile proxy) {
                    if (profile == BluetoothProfile.HEADSET_CLIENT) {
                        Log.d(TAG, "bluetoothAdapter onServiceConnected proxy:" + proxy);
                        mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
                    }
                }

                @Override
                public void onServiceDisconnected(int profile) {
                    Log.d(TAG, "bluetoothAdapter onServiceDisconnected" + profile);
                }
            }, BluetoothProfile.HEADSET_CLIENT);
        }
    }
    
public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
   
 }

Android 9.0 设置去取热点打开的首页显示的提示

--- a/ac8257_0302/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/dashboard/conditional/ConditionManager.java
+++ b/ac8257_0302/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/dashboard/conditional/ConditionManager.java
@@ -42,7 +42,7 @@ public class ConditionManager implements LifecycleObserver, OnResume, OnPause {
 
     private static final String TAG = "ConditionManager";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     private static final String PKG = "com.android.settings.dashboard.conditional.";
 
@@ -176,7 +176,9 @@ public class ConditionManager implements LifecycleObserver, OnResume, OnPause {
         if (AirplaneModeCondition.class == clz) {
             return new AirplaneModeCondition(this);
         } else if (HotspotCondition.class == clz) {
-            return new HotspotCondition(this);
+            //return new HotspotCondition(this);//add 
+            Log.d(TAG,"no new HotspotCondition(this)");
+            return null;
         } else if (DndCondition.class == clz) {
             return new DndCondition(this);
         } else if (BatterySaverCondition.class == clz) {