diff options
author | Prabhakaran Mc <prabhakaranmc@codeaurora.org> | 2011-04-26 11:58:00 +0530 |
---|---|---|
committer | Chris Boyle <chris@boyle.name> | 2012-03-30 22:34:55 +0100 |
commit | 0148fc9a776f2da78d6321bf245e42de9b5bd417 (patch) | |
tree | 9dd3dae92347cb3f40f16d1e1cdb303f99c22744 | |
parent | 40c519edc38563a1017596d8559d5f8efd765cf6 (diff) | |
download | frameworks_base-0148fc9a776f2da78d6321bf245e42de9b5bd417.zip frameworks_base-0148fc9a776f2da78d6321bf245e42de9b5bd417.tar.gz frameworks_base-0148fc9a776f2da78d6321bf245e42de9b5bd417.tar.bz2 |
frameworks/base: bluetooth: AVRCP 1.3 feature changes
Bluetooth module will listen metadata/event change intents
from Music app and post data to Bluez stack.
Modified by Chris Boyle: removed PLAYSTATUS_REQUEST/RESPONSE.
See http://review.cyanogenmod.com/14001
Conflicts:
core/java/android/server/BluetoothA2dpService.java
core/jni/android_server_BluetoothEventLoop.cpp
Gingerbread change Icbfb1712dde98c9677f57c869bcfffed2bb42a14
Change-Id: Id8cbec68302b53befbec4273013668a58c755369
-rw-r--r-- | core/java/android/server/BluetoothA2dpService.java | 165 | ||||
-rw-r--r-- | core/jni/android_server_BluetoothA2dpService.cpp | 123 | ||||
-rw-r--r-- | core/jni/android_server_BluetoothEventLoop.cpp | 13 |
3 files changed, 300 insertions, 1 deletions
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index c4cb3a5..ddc022d 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -66,6 +66,36 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private int mTargetA2dpState; private BluetoothDevice mPlayingA2dpDevice; + /* 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 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 BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -109,10 +139,122 @@ 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); + } } } }; + 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"); + for (String path: getConnectedSinksPaths()) { + sendPlayStatus(path); + } + } + private boolean isPhoneDocked(BluetoothDevice device) { // This works only because these broadcast intents are "sticky" Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); @@ -148,6 +290,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + mIntentFilter.addAction(PLAYSTATE_CHANGED); + mIntentFilter.addAction(META_CHANGED); mContext.registerReceiver(mReceiver, mIntentFilter); mAudioDevices = new HashMap<BluetoothDevice, Integer>(); @@ -397,6 +541,16 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return state; } + public synchronized List<String> getConnectedSinksPaths() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + List<BluetoothDevice> btDevices = getConnectedDevices(); + ArrayList<String> paths = new ArrayList<String>(); + for(BluetoothDevice device:btDevices) { + paths.add(mBluetoothService.getObjectPathFromAddress(device.getAddress())); + } + return paths; + } + public synchronized List<BluetoothDevice> getConnectedDevices() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( @@ -526,6 +680,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.A2DP, state, prevState); } + if (prevState == BluetoothA2dp.STATE_CONNECTING && + state == BluetoothA2dp.STATE_CONNECTED) { + for (String path: getConnectedSinksPaths()) { + sendMetaData(path); + sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus); + } + } } private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) { @@ -609,4 +770,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private synchronized native Object []getSinkPropertiesNative(String path); private synchronized native boolean avrcpVolumeUpNative(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); } diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp index 1851ad6..51d25c0 100644 --- a/core/jni/android_server_BluetoothA2dpService.cpp +++ b/core/jni/android_server_BluetoothA2dpService.cpp @@ -39,6 +39,13 @@ namespace android { #ifdef HAVE_BLUETOOTH static jmethodID method_onSinkPropertyChanged; 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 { JavaVM *vm; @@ -49,6 +56,7 @@ typedef struct { static native_data_t *nat = NULL; // global native data static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); +static void onStatusReply(DBusMessage *msg, void *user, void *n); static Properties sink_properties[] = { {"State", DBUS_TYPE_STRING}, @@ -216,6 +224,92 @@ static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object, 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, jstring path) { #ifdef HAVE_BLUETOOTH @@ -264,7 +358,12 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; return result; - } else { + } 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 { LOGV("... ignored"); } if (env->ExceptionCheck()) { @@ -304,6 +403,17 @@ void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { 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 @@ -321,6 +431,9 @@ static JNINativeMethod sMethods[] = { (void *)getSinkPropertiesNative}, {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, {"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) { @@ -335,6 +448,14 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) { "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", "(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 return AndroidRuntime::registerNativeMethods(env, diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index e8933fe..192e1e3 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -288,6 +288,13 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); 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; + } dbus_bus_add_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'", @@ -455,6 +462,12 @@ static void tearDownEventLoop(native_data_t *nat) { 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, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Device'", &err); if (dbus_error_is_set(&err)) { |