Loading core/java/android/server/BluetoothA2dpService.java +198 −0 Original line number Original line Diff line number Diff line Loading @@ -72,6 +72,53 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final BluetoothAdapter mAdapter; private final BluetoothAdapter mAdapter; private int mTargetA2dpState; private int mTargetA2dpState; /* AVRCP1.3 Metadata variables */ private String mTrackName = DEFAULT_METADATA_STRING; private String mArtistName = DEFAULT_METADATA_STRING; private String mAlbumName = DEFAULT_METADATA_STRING; private String mMediaNumber = DEFAULT_METADATA_NUMBER; private String mMediaCount = DEFAULT_METADATA_NUMBER; private String mDuration = DEFAULT_METADATA_NUMBER; private int mPlayStatus = (int)Integer.valueOf(DEFAULT_METADATA_NUMBER); private long mPosition = (long)Long.valueOf(DEFAULT_METADATA_NUMBER); /* AVRCP1.3 Events */ private final static int EVENT_PLAYSTATUS_CHANGED = 0x1; private final static int EVENT_TRACK_CHANGED = 0x2; /*AVRCP 1.3 Music App Intents */ private static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; private static final String META_CHANGED = "com.android.music.metachanged"; private static final String PLAYSTATUS_REQUEST = "com.android.music.playstatusrequest"; private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; private final static String DEFAULT_METADATA_STRING = "Unknown"; private final static String DEFAULT_METADATA_NUMBER = "0"; /* AVRCP 1.3 PlayStatus */ private final static int STATUS_STOPPED = 0X00; private final static int STATUS_PLAYING = 0X01; private final static int STATUS_PAUSED = 0X02; private final static int STATUS_FWD_SEEK = 0X03; private final static int STATUS_REV_SEEK = 0X04; private final static int STATUS_ERROR = 0XFF; private final static int MESSAGE_PLAYSTATUS_TIMEOUT = 1; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_PLAYSTATUS_TIMEOUT: for (String path: getConnectedSinksPaths()) { Log.i(TAG, "Timed outM - Sending Playstatus"); sendPlayStatus(path); } } } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @Override public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) { Loading Loading @@ -127,10 +174,136 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } } } } } } else if (action.equals(META_CHANGED)) { mTrackName = intent.getStringExtra("track"); mArtistName = intent.getStringExtra("artist"); mAlbumName = intent.getStringExtra("album"); if (mTrackName == null) mTrackName = DEFAULT_METADATA_STRING; if (mArtistName == null) mArtistName = DEFAULT_METADATA_STRING; if (mAlbumName == null) mAlbumName = DEFAULT_METADATA_STRING; long extra = intent.getLongExtra("id", 0); if (extra < 0) extra = 0; mMediaNumber = String.valueOf(extra); extra = intent.getLongExtra("ListSize", 0);; if (extra < 0) extra = 0; mMediaCount = String.valueOf(extra); extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); extra = intent.getLongExtra("position", 0); if (extra < 0) extra = 0; mPosition = extra; if(DBG) { Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName); Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount); Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration); } for (String path: getConnectedSinksPaths()) { sendMetaData(path); sendEvent(path, EVENT_TRACK_CHANGED, Long.valueOf(mMediaNumber)); } } else if (action.equals(PLAYSTATE_CHANGED)) { String currentTrackName = intent.getStringExtra("track"); if ((currentTrackName != null) && (!currentTrackName.equals(mTrackName))) { mTrackName = currentTrackName; mArtistName = intent.getStringExtra("artist"); mAlbumName = intent.getStringExtra("album"); if (mTrackName == null) mTrackName = DEFAULT_METADATA_STRING; if (mArtistName == null) mArtistName = DEFAULT_METADATA_STRING; if (mAlbumName == null) mAlbumName = DEFAULT_METADATA_STRING; long extra = intent.getLongExtra("id", 0); if (extra < 0) extra = 0; mMediaNumber = String.valueOf(extra); extra = intent.getLongExtra("ListSize", 0);; if (extra < 0) extra = 0; mMediaCount = String.valueOf(extra); extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); extra = intent.getLongExtra("position", 0); if (extra < 0) extra = 0; mPosition = extra; for (String path: getConnectedSinksPaths()) sendMetaData(path); } boolean playStatus = intent.getBooleanExtra("playing", false); mPosition = intent.getLongExtra("position", 0); if (mPosition < 0) mPosition = 0; mPlayStatus = convertedPlayStatus(playStatus, mPosition); if(DBG) Log.d(TAG, "PlayState changed "+ mPlayStatus); for (String path: getConnectedSinksPaths()) { sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus); } } else if (action.equals(PLAYSTATUS_RESPONSE)) { if(DBG) Log.d(TAG, "Received PLAYSTATUS_RESPONSE"); long extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); mPosition = intent.getLongExtra("position", 0); if (mPosition < 0) mPosition = 0; boolean playStatus = intent.getBooleanExtra("playing", false); mPlayStatus = convertedPlayStatus(playStatus, mPosition); for (String path: getConnectedSinksPaths()) { if(DBG) Log.d(TAG, "Sending Playstatus"); sendPlayStatus(path); } } } } } }; }; private synchronized int convertedPlayStatus(boolean playing, long position) { if (playing == false && position == 0) return STATUS_STOPPED; if (playing == false) return STATUS_PAUSED; if (playing == true) return STATUS_PLAYING; return STATUS_ERROR; } private synchronized void sendMetaData(String path) { if(DBG) { Log.d(TAG, "sendMetaData "+ path); Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName); Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount); Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration); } sendMetaDataNative(path); } private synchronized void sendEvent(String path, int eventId, long data) { if(DBG) Log.d(TAG, "sendEvent "+path+ " data "+ data); sendEventNative(path, eventId, data); } private synchronized void sendPlayStatus(String path) { if(DBG) Log.d(TAG, "sendPlayStatus"+ path); sendPlayStatusNative(path, (int)Integer.valueOf(mDuration), (int)mPosition, mPlayStatus); } private void onGetPlayStatusRequest() { if(DBG) Log.d(TAG, "onGetPlayStatusRequest"); mContext.sendBroadcast(new Intent(PLAYSTATUS_REQUEST)); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_PLAYSTATUS_TIMEOUT), 130); } private boolean isPhoneDocked(BluetoothDevice device) { private boolean isPhoneDocked(BluetoothDevice device) { // This works only because these broadcast intents are "sticky" // This works only because these broadcast intents are "sticky" Loading Loading @@ -168,6 +341,9 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); mIntentFilter.addAction(PLAYSTATE_CHANGED); mIntentFilter.addAction(META_CHANGED); mIntentFilter.addAction(PLAYSTATUS_RESPONSE); mContext.registerReceiver(mReceiver, mIntentFilter); mContext.registerReceiver(mReceiver, mIntentFilter); mAudioDevices = new HashMap<BluetoothDevice, Integer>(); mAudioDevices = new HashMap<BluetoothDevice, Integer>(); Loading Loading @@ -422,6 +598,17 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return sinks.toArray(new BluetoothDevice[sinks.size()]); return sinks.toArray(new BluetoothDevice[sinks.size()]); } } public synchronized String[] getConnectedSinksPaths() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); BluetoothDevice[] btDevices = getConnectedSinks(); String[] paths = new String[btDevices.length]; int index = 0; for(BluetoothDevice device:btDevices) { paths[index++] = mBluetoothService.getObjectPathFromAddress(device.getAddress()); } return paths; } public synchronized BluetoothDevice[] getNonDisconnectedSinks() { public synchronized BluetoothDevice[] getNonDisconnectedSinks() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Set<BluetoothDevice> sinks = lookupSinksMatchingStates( Set<BluetoothDevice> sinks = lookupSinksMatchingStates( Loading Loading @@ -531,6 +718,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); } } if (prevState == BluetoothA2dp.STATE_CONNECTING && state == BluetoothA2dp.STATE_CONNECTED) { for (String path: getConnectedSinksPaths()) { sendMetaData(path); sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus); } } } } private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { Loading Loading @@ -613,4 +807,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private synchronized native Object []getSinkPropertiesNative(String path); private synchronized native Object []getSinkPropertiesNative(String path); private synchronized native boolean avrcpVolumeUpNative(String path); private synchronized native boolean avrcpVolumeUpNative(String path); private synchronized native boolean avrcpVolumeDownNative(String path); private synchronized native boolean avrcpVolumeDownNative(String path); private synchronized native boolean sendMetaDataNative(String path); private synchronized native boolean sendEventNative(String path, int eventId, long data); private synchronized native boolean sendPlayStatusNative(String path, int duration, int position, int playStatus); } } core/jni/android_server_BluetoothA2dpService.cpp +122 −1 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,13 @@ namespace android { #ifdef HAVE_BLUETOOTH #ifdef HAVE_BLUETOOTH static jmethodID method_onSinkPropertyChanged; static jmethodID method_onSinkPropertyChanged; static jmethodID method_onConnectSinkResult; static jmethodID method_onConnectSinkResult; static jmethodID method_onGetPlayStatusRequest; static jfieldID field_mTrackName; static jfieldID field_mArtistName; static jfieldID field_mAlbumName; static jfieldID field_mMediaNumber; static jfieldID field_mMediaCount; static jfieldID field_mDuration; typedef struct { typedef struct { JavaVM *vm; JavaVM *vm; Loading @@ -49,6 +56,7 @@ typedef struct { static native_data_t *nat = NULL; // global native data static native_data_t *nat = NULL; // global native data static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); static void onStatusReply(DBusMessage *msg, void *user, void *n); static Properties sink_properties[] = { static Properties sink_properties[] = { {"State", DBUS_TYPE_STRING}, {"State", DBUS_TYPE_STRING}, Loading Loading @@ -216,6 +224,92 @@ static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object, return JNI_FALSE; return JNI_FALSE; } } static jboolean sendMetaDataNative(JNIEnv *env, jobject obj, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { jstring title, artist, album, media_number, total_media_count, playing_time; const char *c_title, *c_artist, *c_album, *c_media_number; const char *c_total_media_count, *c_playing_time; const char *c_path = env->GetStringUTFChars(path, NULL); title = (jstring) env->GetObjectField(obj, field_mTrackName); artist = (jstring) env->GetObjectField(obj, field_mArtistName); album = (jstring) env->GetObjectField(obj, field_mAlbumName); media_number = (jstring) env->GetObjectField(obj, field_mMediaNumber); total_media_count = (jstring) env->GetObjectField(obj, field_mMediaCount); playing_time = (jstring) env->GetObjectField(obj, field_mDuration); c_title = env->GetStringUTFChars(title, NULL); c_artist = env->GetStringUTFChars(artist, NULL); c_album = env->GetStringUTFChars(album, NULL); c_media_number = env->GetStringUTFChars(media_number, NULL); c_total_media_count = env->GetStringUTFChars(total_media_count, NULL); c_playing_time = env->GetStringUTFChars(playing_time, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdateMetaData", DBUS_TYPE_STRING, &c_title, DBUS_TYPE_STRING, &c_artist, DBUS_TYPE_STRING, &c_album, DBUS_TYPE_STRING, &c_media_number, DBUS_TYPE_STRING, &c_total_media_count, DBUS_TYPE_STRING, &c_playing_time, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); env->ReleaseStringUTFChars(title, c_title); env->ReleaseStringUTFChars(artist, c_artist); env->ReleaseStringUTFChars(album, c_album); env->ReleaseStringUTFChars(media_number, c_media_number); env->ReleaseStringUTFChars(total_media_count, c_total_media_count); env->ReleaseStringUTFChars(playing_time, c_playing_time); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean sendPlayStatusNative(JNIEnv *env, jobject object, jstring path, jint duration, jint position, jint play_status) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdatePlayStatus", DBUS_TYPE_UINT32, &duration, DBUS_TYPE_UINT32, &position, DBUS_TYPE_UINT32, &play_status, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean sendEventNative(JNIEnv *env, jobject object, jstring path, jint event_id, jlong data) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdateNotification", DBUS_TYPE_UINT16, &event_id, DBUS_TYPE_UINT64, &data, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object, static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object, jstring path) { jstring path) { #ifdef HAVE_BLUETOOTH #ifdef HAVE_BLUETOOTH Loading Loading @@ -264,6 +358,11 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { env->DeleteLocalRef(path); env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; result = DBUS_HANDLER_RESULT_HANDLED; return result; return result; } else if (dbus_message_is_signal(msg, "org.bluez.Control", "GetPlayStatus")) { env->CallVoidMethod(nat->me, method_onGetPlayStatusRequest); result = DBUS_HANDLER_RESULT_HANDLED; return result; }else { }else { LOGV("... ignored"); LOGV("... ignored"); } } Loading Loading @@ -304,6 +403,17 @@ void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { free(user); free(user); } } void onStatusReply(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); native_data_t *nat = (native_data_t *)n; DBusError err; dbus_error_init(&err); if (dbus_set_error_from_message(&err, msg)) { LOG_AND_FREE_DBUS_ERROR(&err); } } #endif #endif Loading @@ -321,6 +431,9 @@ static JNINativeMethod sMethods[] = { (void *)getSinkPropertiesNative}, (void *)getSinkPropertiesNative}, {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative}, {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative}, {"sendMetaDataNative", "(Ljava/lang/String;)Z", (void*)sendMetaDataNative}, {"sendEventNative", "(Ljava/lang/String;IJ)Z", (void*)sendEventNative}, {"sendPlayStatusNative", "(Ljava/lang/String;III)Z", (void*)sendPlayStatusNative}, }; }; int register_android_server_BluetoothA2dpService(JNIEnv *env) { int register_android_server_BluetoothA2dpService(JNIEnv *env) { Loading @@ -335,6 +448,14 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) { "(Ljava/lang/String;[Ljava/lang/String;)V"); "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", "(Ljava/lang/String;Z)V"); "(Ljava/lang/String;Z)V"); method_onGetPlayStatusRequest = env->GetMethodID(clazz, "onGetPlayStatusRequest", "()V"); field_mTrackName = env->GetFieldID(clazz, "mTrackName", "Ljava/lang/String;"); field_mArtistName = env->GetFieldID(clazz, "mArtistName", "Ljava/lang/String;"); field_mAlbumName = env->GetFieldID(clazz, "mAlbumName", "Ljava/lang/String;"); field_mMediaNumber = env->GetFieldID(clazz, "mMediaNumber", "Ljava/lang/String;"); field_mMediaCount = env->GetFieldID(clazz, "mMediaCount", "Ljava/lang/String;"); field_mDuration = env->GetFieldID(clazz, "mDuration", "Ljava/lang/String;"); #endif #endif return AndroidRuntime::registerNativeMethods(env, return AndroidRuntime::registerNativeMethods(env, Loading core/jni/android_server_BluetoothEventLoop.cpp +13 −0 Original line number Original line Diff line number Diff line Loading @@ -238,6 +238,13 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; return JNI_FALSE; } } dbus_bus_add_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'", &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } const char *agent_path = "/android/bluetooth/agent"; const char *agent_path = "/android/bluetooth/agent"; const char *capabilities = "DisplayYesNo"; const char *capabilities = "DisplayYesNo"; Loading Loading @@ -395,6 +402,12 @@ static void tearDownEventLoop(native_data_t *nat) { if (dbus_error_is_set(&err)) { if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); LOG_AND_FREE_DBUS_ERROR(&err); } } dbus_bus_remove_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'", &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); } dbus_bus_remove_match(nat->conn, dbus_bus_remove_match(nat->conn, "type='signal',interface='org.bluez.Device'", "type='signal',interface='org.bluez.Device'", &err); &err); Loading Loading
core/java/android/server/BluetoothA2dpService.java +198 −0 Original line number Original line Diff line number Diff line Loading @@ -72,6 +72,53 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final BluetoothAdapter mAdapter; private final BluetoothAdapter mAdapter; private int mTargetA2dpState; private int mTargetA2dpState; /* AVRCP1.3 Metadata variables */ private String mTrackName = DEFAULT_METADATA_STRING; private String mArtistName = DEFAULT_METADATA_STRING; private String mAlbumName = DEFAULT_METADATA_STRING; private String mMediaNumber = DEFAULT_METADATA_NUMBER; private String mMediaCount = DEFAULT_METADATA_NUMBER; private String mDuration = DEFAULT_METADATA_NUMBER; private int mPlayStatus = (int)Integer.valueOf(DEFAULT_METADATA_NUMBER); private long mPosition = (long)Long.valueOf(DEFAULT_METADATA_NUMBER); /* AVRCP1.3 Events */ private final static int EVENT_PLAYSTATUS_CHANGED = 0x1; private final static int EVENT_TRACK_CHANGED = 0x2; /*AVRCP 1.3 Music App Intents */ private static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; private static final String META_CHANGED = "com.android.music.metachanged"; private static final String PLAYSTATUS_REQUEST = "com.android.music.playstatusrequest"; private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; private final static String DEFAULT_METADATA_STRING = "Unknown"; private final static String DEFAULT_METADATA_NUMBER = "0"; /* AVRCP 1.3 PlayStatus */ private final static int STATUS_STOPPED = 0X00; private final static int STATUS_PLAYING = 0X01; private final static int STATUS_PAUSED = 0X02; private final static int STATUS_FWD_SEEK = 0X03; private final static int STATUS_REV_SEEK = 0X04; private final static int STATUS_ERROR = 0XFF; private final static int MESSAGE_PLAYSTATUS_TIMEOUT = 1; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_PLAYSTATUS_TIMEOUT: for (String path: getConnectedSinksPaths()) { Log.i(TAG, "Timed outM - Sending Playstatus"); sendPlayStatus(path); } } } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @Override public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) { Loading Loading @@ -127,10 +174,136 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } } } } } } else if (action.equals(META_CHANGED)) { mTrackName = intent.getStringExtra("track"); mArtistName = intent.getStringExtra("artist"); mAlbumName = intent.getStringExtra("album"); if (mTrackName == null) mTrackName = DEFAULT_METADATA_STRING; if (mArtistName == null) mArtistName = DEFAULT_METADATA_STRING; if (mAlbumName == null) mAlbumName = DEFAULT_METADATA_STRING; long extra = intent.getLongExtra("id", 0); if (extra < 0) extra = 0; mMediaNumber = String.valueOf(extra); extra = intent.getLongExtra("ListSize", 0);; if (extra < 0) extra = 0; mMediaCount = String.valueOf(extra); extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); extra = intent.getLongExtra("position", 0); if (extra < 0) extra = 0; mPosition = extra; if(DBG) { Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName); Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount); Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration); } for (String path: getConnectedSinksPaths()) { sendMetaData(path); sendEvent(path, EVENT_TRACK_CHANGED, Long.valueOf(mMediaNumber)); } } else if (action.equals(PLAYSTATE_CHANGED)) { String currentTrackName = intent.getStringExtra("track"); if ((currentTrackName != null) && (!currentTrackName.equals(mTrackName))) { mTrackName = currentTrackName; mArtistName = intent.getStringExtra("artist"); mAlbumName = intent.getStringExtra("album"); if (mTrackName == null) mTrackName = DEFAULT_METADATA_STRING; if (mArtistName == null) mArtistName = DEFAULT_METADATA_STRING; if (mAlbumName == null) mAlbumName = DEFAULT_METADATA_STRING; long extra = intent.getLongExtra("id", 0); if (extra < 0) extra = 0; mMediaNumber = String.valueOf(extra); extra = intent.getLongExtra("ListSize", 0);; if (extra < 0) extra = 0; mMediaCount = String.valueOf(extra); extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); extra = intent.getLongExtra("position", 0); if (extra < 0) extra = 0; mPosition = extra; for (String path: getConnectedSinksPaths()) sendMetaData(path); } boolean playStatus = intent.getBooleanExtra("playing", false); mPosition = intent.getLongExtra("position", 0); if (mPosition < 0) mPosition = 0; mPlayStatus = convertedPlayStatus(playStatus, mPosition); if(DBG) Log.d(TAG, "PlayState changed "+ mPlayStatus); for (String path: getConnectedSinksPaths()) { sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus); } } else if (action.equals(PLAYSTATUS_RESPONSE)) { if(DBG) Log.d(TAG, "Received PLAYSTATUS_RESPONSE"); long extra = intent.getLongExtra("duration", 0); if (extra < 0) extra = 0; mDuration = String.valueOf(extra); mPosition = intent.getLongExtra("position", 0); if (mPosition < 0) mPosition = 0; boolean playStatus = intent.getBooleanExtra("playing", false); mPlayStatus = convertedPlayStatus(playStatus, mPosition); for (String path: getConnectedSinksPaths()) { if(DBG) Log.d(TAG, "Sending Playstatus"); sendPlayStatus(path); } } } } } }; }; private synchronized int convertedPlayStatus(boolean playing, long position) { if (playing == false && position == 0) return STATUS_STOPPED; if (playing == false) return STATUS_PAUSED; if (playing == true) return STATUS_PLAYING; return STATUS_ERROR; } private synchronized void sendMetaData(String path) { if(DBG) { Log.d(TAG, "sendMetaData "+ path); Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName); Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount); Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration); } sendMetaDataNative(path); } private synchronized void sendEvent(String path, int eventId, long data) { if(DBG) Log.d(TAG, "sendEvent "+path+ " data "+ data); sendEventNative(path, eventId, data); } private synchronized void sendPlayStatus(String path) { if(DBG) Log.d(TAG, "sendPlayStatus"+ path); sendPlayStatusNative(path, (int)Integer.valueOf(mDuration), (int)mPosition, mPlayStatus); } private void onGetPlayStatusRequest() { if(DBG) Log.d(TAG, "onGetPlayStatusRequest"); mContext.sendBroadcast(new Intent(PLAYSTATUS_REQUEST)); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_PLAYSTATUS_TIMEOUT), 130); } private boolean isPhoneDocked(BluetoothDevice device) { private boolean isPhoneDocked(BluetoothDevice device) { // This works only because these broadcast intents are "sticky" // This works only because these broadcast intents are "sticky" Loading Loading @@ -168,6 +341,9 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); mIntentFilter.addAction(PLAYSTATE_CHANGED); mIntentFilter.addAction(META_CHANGED); mIntentFilter.addAction(PLAYSTATUS_RESPONSE); mContext.registerReceiver(mReceiver, mIntentFilter); mContext.registerReceiver(mReceiver, mIntentFilter); mAudioDevices = new HashMap<BluetoothDevice, Integer>(); mAudioDevices = new HashMap<BluetoothDevice, Integer>(); Loading Loading @@ -422,6 +598,17 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return sinks.toArray(new BluetoothDevice[sinks.size()]); return sinks.toArray(new BluetoothDevice[sinks.size()]); } } public synchronized String[] getConnectedSinksPaths() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); BluetoothDevice[] btDevices = getConnectedSinks(); String[] paths = new String[btDevices.length]; int index = 0; for(BluetoothDevice device:btDevices) { paths[index++] = mBluetoothService.getObjectPathFromAddress(device.getAddress()); } return paths; } public synchronized BluetoothDevice[] getNonDisconnectedSinks() { public synchronized BluetoothDevice[] getNonDisconnectedSinks() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Set<BluetoothDevice> sinks = lookupSinksMatchingStates( Set<BluetoothDevice> sinks = lookupSinksMatchingStates( Loading Loading @@ -531,6 +718,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); } } if (prevState == BluetoothA2dp.STATE_CONNECTING && state == BluetoothA2dp.STATE_CONNECTED) { for (String path: getConnectedSinksPaths()) { sendMetaData(path); sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus); } } } } private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { Loading Loading @@ -613,4 +807,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private synchronized native Object []getSinkPropertiesNative(String path); private synchronized native Object []getSinkPropertiesNative(String path); private synchronized native boolean avrcpVolumeUpNative(String path); private synchronized native boolean avrcpVolumeUpNative(String path); private synchronized native boolean avrcpVolumeDownNative(String path); private synchronized native boolean avrcpVolumeDownNative(String path); private synchronized native boolean sendMetaDataNative(String path); private synchronized native boolean sendEventNative(String path, int eventId, long data); private synchronized native boolean sendPlayStatusNative(String path, int duration, int position, int playStatus); } }
core/jni/android_server_BluetoothA2dpService.cpp +122 −1 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,13 @@ namespace android { #ifdef HAVE_BLUETOOTH #ifdef HAVE_BLUETOOTH static jmethodID method_onSinkPropertyChanged; static jmethodID method_onSinkPropertyChanged; static jmethodID method_onConnectSinkResult; static jmethodID method_onConnectSinkResult; static jmethodID method_onGetPlayStatusRequest; static jfieldID field_mTrackName; static jfieldID field_mArtistName; static jfieldID field_mAlbumName; static jfieldID field_mMediaNumber; static jfieldID field_mMediaCount; static jfieldID field_mDuration; typedef struct { typedef struct { JavaVM *vm; JavaVM *vm; Loading @@ -49,6 +56,7 @@ typedef struct { static native_data_t *nat = NULL; // global native data static native_data_t *nat = NULL; // global native data static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); static void onStatusReply(DBusMessage *msg, void *user, void *n); static Properties sink_properties[] = { static Properties sink_properties[] = { {"State", DBUS_TYPE_STRING}, {"State", DBUS_TYPE_STRING}, Loading Loading @@ -216,6 +224,92 @@ static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object, return JNI_FALSE; return JNI_FALSE; } } static jboolean sendMetaDataNative(JNIEnv *env, jobject obj, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { jstring title, artist, album, media_number, total_media_count, playing_time; const char *c_title, *c_artist, *c_album, *c_media_number; const char *c_total_media_count, *c_playing_time; const char *c_path = env->GetStringUTFChars(path, NULL); title = (jstring) env->GetObjectField(obj, field_mTrackName); artist = (jstring) env->GetObjectField(obj, field_mArtistName); album = (jstring) env->GetObjectField(obj, field_mAlbumName); media_number = (jstring) env->GetObjectField(obj, field_mMediaNumber); total_media_count = (jstring) env->GetObjectField(obj, field_mMediaCount); playing_time = (jstring) env->GetObjectField(obj, field_mDuration); c_title = env->GetStringUTFChars(title, NULL); c_artist = env->GetStringUTFChars(artist, NULL); c_album = env->GetStringUTFChars(album, NULL); c_media_number = env->GetStringUTFChars(media_number, NULL); c_total_media_count = env->GetStringUTFChars(total_media_count, NULL); c_playing_time = env->GetStringUTFChars(playing_time, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdateMetaData", DBUS_TYPE_STRING, &c_title, DBUS_TYPE_STRING, &c_artist, DBUS_TYPE_STRING, &c_album, DBUS_TYPE_STRING, &c_media_number, DBUS_TYPE_STRING, &c_total_media_count, DBUS_TYPE_STRING, &c_playing_time, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); env->ReleaseStringUTFChars(title, c_title); env->ReleaseStringUTFChars(artist, c_artist); env->ReleaseStringUTFChars(album, c_album); env->ReleaseStringUTFChars(media_number, c_media_number); env->ReleaseStringUTFChars(total_media_count, c_total_media_count); env->ReleaseStringUTFChars(playing_time, c_playing_time); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean sendPlayStatusNative(JNIEnv *env, jobject object, jstring path, jint duration, jint position, jint play_status) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdatePlayStatus", DBUS_TYPE_UINT32, &duration, DBUS_TYPE_UINT32, &position, DBUS_TYPE_UINT32, &play_status, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean sendEventNative(JNIEnv *env, jobject object, jstring path, jint event_id, jlong data) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat, c_path, "org.bluez.Control", "UpdateNotification", DBUS_TYPE_UINT16, &event_id, DBUS_TYPE_UINT64, &data, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return ret ? JNI_TRUE : JNI_FALSE; } #endif return JNI_FALSE; } static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object, static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object, jstring path) { jstring path) { #ifdef HAVE_BLUETOOTH #ifdef HAVE_BLUETOOTH Loading Loading @@ -264,6 +358,11 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { env->DeleteLocalRef(path); env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; result = DBUS_HANDLER_RESULT_HANDLED; return result; return result; } else if (dbus_message_is_signal(msg, "org.bluez.Control", "GetPlayStatus")) { env->CallVoidMethod(nat->me, method_onGetPlayStatusRequest); result = DBUS_HANDLER_RESULT_HANDLED; return result; }else { }else { LOGV("... ignored"); LOGV("... ignored"); } } Loading Loading @@ -304,6 +403,17 @@ void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { free(user); free(user); } } void onStatusReply(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); native_data_t *nat = (native_data_t *)n; DBusError err; dbus_error_init(&err); if (dbus_set_error_from_message(&err, msg)) { LOG_AND_FREE_DBUS_ERROR(&err); } } #endif #endif Loading @@ -321,6 +431,9 @@ static JNINativeMethod sMethods[] = { (void *)getSinkPropertiesNative}, (void *)getSinkPropertiesNative}, {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative}, {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative}, {"sendMetaDataNative", "(Ljava/lang/String;)Z", (void*)sendMetaDataNative}, {"sendEventNative", "(Ljava/lang/String;IJ)Z", (void*)sendEventNative}, {"sendPlayStatusNative", "(Ljava/lang/String;III)Z", (void*)sendPlayStatusNative}, }; }; int register_android_server_BluetoothA2dpService(JNIEnv *env) { int register_android_server_BluetoothA2dpService(JNIEnv *env) { Loading @@ -335,6 +448,14 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) { "(Ljava/lang/String;[Ljava/lang/String;)V"); "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", "(Ljava/lang/String;Z)V"); "(Ljava/lang/String;Z)V"); method_onGetPlayStatusRequest = env->GetMethodID(clazz, "onGetPlayStatusRequest", "()V"); field_mTrackName = env->GetFieldID(clazz, "mTrackName", "Ljava/lang/String;"); field_mArtistName = env->GetFieldID(clazz, "mArtistName", "Ljava/lang/String;"); field_mAlbumName = env->GetFieldID(clazz, "mAlbumName", "Ljava/lang/String;"); field_mMediaNumber = env->GetFieldID(clazz, "mMediaNumber", "Ljava/lang/String;"); field_mMediaCount = env->GetFieldID(clazz, "mMediaCount", "Ljava/lang/String;"); field_mDuration = env->GetFieldID(clazz, "mDuration", "Ljava/lang/String;"); #endif #endif return AndroidRuntime::registerNativeMethods(env, return AndroidRuntime::registerNativeMethods(env, Loading
core/jni/android_server_BluetoothEventLoop.cpp +13 −0 Original line number Original line Diff line number Diff line Loading @@ -238,6 +238,13 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; return JNI_FALSE; } } dbus_bus_add_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'", &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } const char *agent_path = "/android/bluetooth/agent"; const char *agent_path = "/android/bluetooth/agent"; const char *capabilities = "DisplayYesNo"; const char *capabilities = "DisplayYesNo"; Loading Loading @@ -395,6 +402,12 @@ static void tearDownEventLoop(native_data_t *nat) { if (dbus_error_is_set(&err)) { if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); LOG_AND_FREE_DBUS_ERROR(&err); } } dbus_bus_remove_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'", &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); } dbus_bus_remove_match(nat->conn, dbus_bus_remove_match(nat->conn, "type='signal',interface='org.bluez.Device'", "type='signal',interface='org.bluez.Device'", &err); &err); Loading