summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrabhakaran Mc <prabhakaranmc@codeaurora.org>2011-04-26 11:58:00 +0530
committerChris Boyle <chris@boyle.name>2012-03-30 22:34:55 +0100
commit0148fc9a776f2da78d6321bf245e42de9b5bd417 (patch)
tree9dd3dae92347cb3f40f16d1e1cdb303f99c22744
parent40c519edc38563a1017596d8559d5f8efd765cf6 (diff)
downloadframeworks_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.java165
-rw-r--r--core/jni/android_server_BluetoothA2dpService.cpp123
-rw-r--r--core/jni/android_server_BluetoothEventLoop.cpp13
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)) {