This is the fifth day of my participation in Gwen Challenge
Bluetooth Starter guide for Android application developers juejin.cn/post/697019…
We briefly introduced the basic usage process of Bluetooth. In this section, we mainly study how the data of audio playback is transmitted between two devices.
A common scenario is that people connect their mobile phones to their cars via Bluetooth, and the car screen displays the current audio message. The car app can also reverse the current music playback on the phone. In the previous section, Bluetooth communicates data through sockets. So is that true of bluetooth in cars? Let’s explore this process.
The demo result:
The phone app transfers information to the car
Vehicle-mounted reverse control phone:
Understanding profile Files
To put this in perspective, profile represents a capability that defines how devices interact with each other. For example, AVRCP, our cross-device invocation, defines how to make cross-device invocation. Each protocol has its own profile(depending on whether the system has implemented the corresponding function, if not necessary, you can not have the corresponding profile).
How do audio messages get sent to bluetooth
Before android5.0, sending audio messages is done via RemoteControlClient. After 5.0 it is done through MediaSession.
MediaSession a class that controls the volume, buttons, and data transfer of the player. In general, there is only one MediaSession per application.
To receive call control from other devices, call setActive (true) and setCallback(Callback).
Sending of audio messages
Mediassession #setMetadata(@nullable MediaMetadata metadata)
There are many keys defined in MediaMetadata. The data sender only needs to add data according to the corresponding keys
Mediassession #setPlaybackState(@nullable PlaybackState) can send information related to the PlaybackState.
Player data sending:
mediaSession = new MediaSession(BluetoothPlayerActivity.this."test");
mediaSession.setActive(true);
mediaSession.setMetadata(new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, "You are the wind and I am the sand")
.putString(MediaMetadata.METADATA_KEY_ARTIST, "Who am I?")
.build());
Toast.makeText(BluetoothPlayerActivity.this."Sending message already started, reset requires exit reprogress.",Toast.LENGTH_SHORT).show();
AppExecutors.getInstance().networkIO().execute(new Runnable() {
@Override
public void run(a) {
int i = 0;
while(! destroy){ Log.d(TAG,"update PlaybackState "+mediaPlayer.getCurrentPosition());
mediaSession.setPlaybackState(new PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, mediaPlayer.getCurrentPosition(), 1.0 f)
.build());
mediaSession.setMetadata(new MediaMetadata.Builder()
.putLong(MediaMetadata.METADATA_KEY_DURATION, mediaPlayer.getDuration())
.putString(MediaMetadata.METADATA_KEY_TITLE, "You are the wind and I am the sand"+i)
.putString(MediaMetadata.METADATA_KEY_ARTIST, "Who am I?")
.build());
SystemClock.sleep(500); i++; }}});Copy the code
Vehicle audio message reception
When the system receives a change in the audio message, it sends two broadcasts:
So we also register this broadcast to receive audio messages.
BroadcastReceiver avrcpBroadcastReceiver = new BroadcastReceiver() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(AvrcpControllerHelper.ACTION_TRACK_EVENT)){
final MediaMetadata mediaMetadata = intent.getParcelableExtra(AvrcpControllerHelper.EXTRA_METADATA);
final PlaybackState playbackState = intent.getParcelableExtra(AvrcpControllerHelper.EXTRA_PLAYBACK);
// if(mediaMetadata == null || playbackState == null){
// Log.e(TAG,"some info null ? = "+mediaMetadata+" "+playbackState);
// return;
/ /}
if(mediaMetadata ! =null){
name = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
author = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
totalTime = timeCover(+mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
}
if(playbackState ! =null){
currentTime = timeCover(playbackState.getPosition());
play = playbackState.getState() == PlaybackState.STATE_PLAYING;
}
TextView textView = findViewById(R.id.tvPlayTime);
textView.setText("Start time"+currentTime);
textView = findViewById(R.id.tvTotalTime);
textView.setText("Total Duration"+totalTime);
textView = findViewById(R.id.tvMusicInfo);
textView.setText("Title:"+name +"------------ Author:"+author);
textView = findViewById(R.id.tvPlayToggle);
textView.setText("Remote Player"+(play?"Playing now":"Not playing")); }}}; registerReceiver(avrcpBroadcastReceiver,new IntentFilter(AvrcpControllerHelper.ACTION_TRACK_EVENT));
Copy the code
How does bluetooth reverse control the audio player
BluetoothAvrcpController to reverse control the audio player, BluetoothAvrcpController is hidden from normal applications. If there is a framework that can be accessed directly, we can use reflection to handle it if there is not. BluetoothAvrcpController#sendGroupNavigationCmd is used to call the audio player back.
We also set up callback objects for mediaSession
mediaSession.setCallback(new MediaSession.Callback() {
@Override
public void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
Log.e(TAG,"onCommand");
super.onCommand(command, args, cb);
}
@Override
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
Log.e(TAG,"onMediaButtonEvent");
Toast.makeText(BluetoothPlayerActivity.this."I treated you so well, and you manipulated me.",Toast.LENGTH_SHORT).show();
return super.onMediaButtonEvent(mediaButtonIntent); }});Copy the code
Reflection of BluetoothAvrcpController to handle related methods
public class AvrcpControllerHelper {
private static final String TAG = AvrcpControllerHelper.class.getSimpleName();
public static int AVRCP_CONTROLLER = 12;
public static final String ACTION_TRACK_EVENT = "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
public static final String EXTRA_METADATA = "android.bluetooth.avrcp-controller.profile.extra.METADATA";
public static final String EXTRA_PLAYBACK = "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
public static void sendGroupNavigationCmd(BluetoothProfile bluetoothProfile, BluetoothDevice device, int keyCode, int keyState){
if(bluetoothProfile ! =null) {try {
Method m = bluetoothProfile.getClass().getMethod("sendGroupNavigationCmd",BluetoothDevice.class,int.class,int.class);
m.setAccessible(true);
m.invoke(bluetoothProfile,device,keyCode,keyState);
} catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }}}private static void fixSystemHideApi(a){
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return;
}
try {
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); Class<? > vmRuntimeClass = (Class<? >) forName.invoke(null."dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime".null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions".new Class[]{String[].class});
Object sVmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{new String[]{"L"}});
} catch (Throwable e) {
Log.e("[error]"."reflect bootstrap failed:", e); }}public static List<BluetoothDevice> getConnectedDevices(BluetoothProfile profile){
if(profile == null) {return null;
}
fixSystemHideApi();
try {
Method m = profile.getClass().getDeclaredMethod("getConnectedDevices");
m.setAccessible(true);
return (List<BluetoothDevice>) m.invoke(profile);
} catch (NoSuchMethodException e) {
Log.w(TAG, "No disconnect method in the " + profile.getClass().getName() +
" class, ignoring request.");
return null;
} catch (InvocationTargetException | IllegalAccessException e) {
Log.w(TAG, "Could not execute method 'disconnect' in profile " +
profile.getClass().getName() + ", ignoring request.", e);
return null; }}// BluetoothAvrcp of the system is not available, here is a copy of this
public static class BluetoothAvrcp {
/* * State flags for Passthrough commands */
public static final int PASSTHROUGH_STATE_PRESS = 0;
public static final int PASSTHROUGH_STATE_RELEASE = 1;
/* * Operation IDs for Passthrough commands */
public static final int PASSTHROUGH_ID_SELECT = 0x00; /* select */
public static final int PASSTHROUGH_ID_UP = 0x01; /* up */
public static final int PASSTHROUGH_ID_DOWN = 0x02; /* down */
public static final int PASSTHROUGH_ID_LEFT = 0x03; /* left */
public static final int PASSTHROUGH_ID_RIGHT = 0x04; /* right */
public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05; /* right-up */
public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06; /* right-down */
public static final int PASSTHROUGH_ID_LEFT_UP = 0x07; /* left-up */
public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08; /* left-down */
public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09; /* root menu */
public static final int PASSTHROUGH_ID_SETUP_MENU = 0x0A; /* setup menu */
public static final int PASSTHROUGH_ID_CONT_MENU = 0x0B; /* contents menu */
public static final int PASSTHROUGH_ID_FAV_MENU = 0x0C; /* favorite menu */
public static final int PASSTHROUGH_ID_EXIT = 0x0D; /* exit */
public static final int PASSTHROUGH_ID_0 = 0x20; /* 0 */
public static final int PASSTHROUGH_ID_1 = 0x21; / * 1 * /
public static final int PASSTHROUGH_ID_2 = 0x22; / * 2 * /
public static final int PASSTHROUGH_ID_3 = 0x23; / * * / 3
public static final int PASSTHROUGH_ID_4 = 0x24; / * * / 4
public static final int PASSTHROUGH_ID_5 = 0x25; / * * / 5
public static final int PASSTHROUGH_ID_6 = 0x26; / * * / 6
public static final int PASSTHROUGH_ID_7 = 0x27; / * * / 7
public static final int PASSTHROUGH_ID_8 = 0x28; /* 8 */
public static final int PASSTHROUGH_ID_9 = 0x29; /* 9 */
public static final int PASSTHROUGH_ID_DOT = 0x2A; /* dot */
public static final int PASSTHROUGH_ID_ENTER = 0x2B; /* enter */
public static final int PASSTHROUGH_ID_CLEAR = 0x2C; /* clear */
public static final int PASSTHROUGH_ID_CHAN_UP = 0x30; /* channel up */
public static final int PASSTHROUGH_ID_CHAN_DOWN = 0x31; /* channel down */
public static final int PASSTHROUGH_ID_PREV_CHAN = 0x32; /* previous channel */
public static final int PASSTHROUGH_ID_SOUND_SEL = 0x33; /* sound select */
public static final int PASSTHROUGH_ID_INPUT_SEL = 0x34; /* input select */
public static final int PASSTHROUGH_ID_DISP_INFO = 0x35; /* display information */
public static final int PASSTHROUGH_ID_HELP = 0x36; /* help */
public static final int PASSTHROUGH_ID_PAGE_UP = 0x37; /* page up */
public static final int PASSTHROUGH_ID_PAGE_DOWN = 0x38; /* page down */
public static final int PASSTHROUGH_ID_POWER = 0x40; /* power */
public static final int PASSTHROUGH_ID_VOL_UP = 0x41; /* volume up */
public static final int PASSTHROUGH_ID_VOL_DOWN = 0x42; /* volume down */
public static final int PASSTHROUGH_ID_MUTE = 0x43; /* mute */
public static final int PASSTHROUGH_ID_PLAY = 0x44; /* play */
public static final int PASSTHROUGH_ID_STOP = 0x45; /* stop */
public static final int PASSTHROUGH_ID_PAUSE = 0x46; /* pause */
public static final int PASSTHROUGH_ID_RECORD = 0x47; /* record */
public static final int PASSTHROUGH_ID_REWIND = 0x48; /* rewind */
public static final int PASSTHROUGH_ID_FAST_FOR = 0x49; /* fast forward */
public static final int PASSTHROUGH_ID_EJECT = 0x4A; /* eject */
public static final int PASSTHROUGH_ID_FORWARD = 0x4B; /* forward */
public static final int PASSTHROUGH_ID_BACKWARD = 0x4C; /* backward */
public static final int PASSTHROUGH_ID_ANGLE = 0x50; /* angle */
public static final int PASSTHROUGH_ID_SUBPICT = 0x51; /* subpicture */
public static final int PASSTHROUGH_ID_F1 = 0x71; /* F1 */
public static final int PASSTHROUGH_ID_F2 = 0x72; /* F2 */
public static final int PASSTHROUGH_ID_F3 = 0x73; /* F3 */
public static final int PASSTHROUGH_ID_F4 = 0x74; /* F4 */
public static final int PASSTHROUGH_ID_F5 = 0x75; /* F5 */
public static final int PASSTHROUGH_ID_VENDOR = 0x7E; /* vendor unique */
public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80; }}Copy the code
Overall use of demo (only through the mutual call process between car and mobile app) :
Github.com/xiaolutang/…
Next up:
Analyze the process of Bluetooth interaction from system flow.