OSDN Git Service

A2DP Sink:Audio Rendering patch and AutoConnect Functionality
authorSanket Agarwal <sanketa@google.com>
Tue, 22 Dec 2015 21:45:00 +0000 (13:45 -0800)
committerSanket Agarwal <sanketa@google.com>
Tue, 19 Jan 2016 22:39:08 +0000 (14:39 -0800)
 -  Add autoconnect functionality for A2DP Sink
 -  Use AudioPatch mechanism for sink playback
 -  AudioFocus approach to manage concurrencies

A2DP Sink: Support for AudioTrack

- add support for audiotrack to render audio data
- add support for AVRCP State to trigger audio
  rendering

AVRCP_CTRL: add support for AVRCP 1.3 Controller

- fill placeholder function for AVRCP 1.3
- add supporting classes to handle AVRCP Commands

AVRCP Controller: Support for retaining volume level and blocking streaming from remote.

- retain volume level on new connection request
- block streaming from remote.

Change-Id: I8c31fd1779b196ced0fb0870855b93263ea331ec

AndroidManifest.xml
jni/com_android_bluetooth_a2dp_sink.cpp
jni/com_android_bluetooth_avrcp_controller.cpp
src/com/android/bluetooth/a2dp/A2dpSinkService.java
src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/AvrcpControllerService.java
src/com/android/bluetooth/avrcp/NowPlaying.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java [new file with mode: 0644]
src/com/android/bluetooth/btservice/AdapterService.java

index 3652fd3..0120363 100644 (file)
@@ -62,6 +62,7 @@
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
index f2bbb1b..1033e83 100644 (file)
@@ -29,6 +29,7 @@ namespace android {
 static jmethodID method_onConnectionStateChanged;
 static jmethodID method_onAudioStateChanged;
 static jmethodID method_onAudioConfigChanged;
+static jmethodID method_onAudioFocusRequested;
 
 static const btav_interface_t *sBluetoothA2dpInterface = NULL;
 static jobject mCallbacksObj = NULL;
@@ -116,11 +117,34 @@ static void bta2dp_audio_config_callback(bt_bdaddr_t *bd_addr, uint32_t sample_r
     sCallbackEnv->DeleteLocalRef(addr);
 }
 
+static void bta2dp_audio_focus_request_callback(bt_bdaddr_t *bd_addr) {
+    jbyteArray addr;
+
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if (!addr) {
+        ALOGE("Fail to new jbyteArray bd addr for connection state");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioFocusRequested, addr);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
 static btav_callbacks_t sBluetoothA2dpCallbacks = {
     sizeof(sBluetoothA2dpCallbacks),
     bta2dp_connection_state_callback,
     bta2dp_audio_state_callback,
-    bta2dp_audio_config_callback
+    bta2dp_audio_config_callback,
+    bta2dp_audio_focus_request_callback
 };
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -137,6 +161,9 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
     method_onAudioConfigChanged =
         env->GetMethodID(clazz, "onAudioConfigChanged", "([BII)V");
 
+    method_onAudioFocusRequested =
+        env->GetMethodID(clazz, "onAudioFocusRequested", "([B)V");
+
     ALOGI("%s: succeeds", __FUNCTION__);
 }
 
@@ -237,12 +264,19 @@ static jboolean disconnectA2dpNative(JNIEnv *env, jobject object, jbyteArray add
     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static void informAudioFocusStateNative(JNIEnv *env, jobject object, jint focus_state) {
+    if (!sBluetoothA2dpInterface) return;
+
+    sBluetoothA2dpInterface->audio_focus_state((uint8_t)focus_state);
+
+}
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void *) classInitNative},
     {"initNative", "()V", (void *) initNative},
     {"cleanupNative", "()V", (void *) cleanupNative},
     {"connectA2dpNative", "([B)Z", (void *) connectA2dpNative},
     {"disconnectA2dpNative", "([B)Z", (void *) disconnectA2dpNative},
+    {"informAudioFocusStateNative", "(I)V", (void *) informAudioFocusStateNative},
 };
 
 int register_com_android_bluetooth_a2dp_sink(JNIEnv* env)
index 9e32721..2c07a57 100644 (file)
 namespace android {
 static jmethodID method_handlePassthroughRsp;
 static jmethodID method_onConnectionStateChanged;
+static jmethodID method_getRcFeatures;
+static jmethodID method_setplayerappsettingrsp;
+static jmethodID method_handleplayerappsetting;
+static jmethodID method_handleplayerappsettingchanged;
+static jmethodID method_handleSetAbsVolume;
+static jmethodID method_handleRegisterNotificationAbsVol;
+static jmethodID method_handletrackchanged;
+static jmethodID method_handleplaypositionchanged;
+static jmethodID method_handleplaystatuschanged;
+static jmethodID method_handleGroupNavigationRsp;
+
 
 static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
 static jobject mCallbacksObj = NULL;
@@ -59,6 +70,19 @@ static void btavrcp_passthrough_response_callback(int id, int pressed) {
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
 }
 
+static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
+                                                                             (jint)pressed);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
 static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr) {
     jbyteArray addr;
 
@@ -84,20 +108,361 @@ static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr)
     sCallbackEnv->DeleteLocalRef(addr);
 }
 
+static void btavrcp_get_rcfeatures_callback(bt_bdaddr_t *bd_addr, int features) {
+    jbyteArray addr;
+
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if (!addr) {
+        ALOGE("Fail to new jbyteArray bd addr ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_setplayerapplicationsetting_rsp_callback(bt_bdaddr_t *bd_addr,
+                                                                    uint8_t accepted) {
+    jbyteArray addr;
+
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if (!addr) {
+        ALOGE("Fail to new jbyteArray bd addr ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_playerapplicationsetting_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
+        btrc_player_app_attr_t *app_attrs, uint8_t num_ext_attr,
+        btrc_player_app_ext_attr_t *ext_attrs) {
+    ALOGI("%s", __FUNCTION__);
+    jbyteArray addr;
+    jbyteArray playerattribs;
+    jint arraylen;
+    int i,k;
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if (!addr) {
+        ALOGE("Fail to new jbyteArray bd addr ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+    /* TODO ext attrs
+     * Flattening defined attributes: <id,num_values,values[]>
+     */
+    arraylen = 0;
+    for (i = 0; i < num_attr; i++)
+    {
+        /*2 bytes for id and num */
+        arraylen += 2 + app_attrs[i].num_val;
+    }
+    ALOGI(" arraylen %d", arraylen);
+    playerattribs = sCallbackEnv->NewByteArray(arraylen);
+    if(!playerattribs)
+    {
+        ALOGE("Fail to new jbyteArray playerattribs ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        sCallbackEnv->DeleteLocalRef(addr);
+        return;
+    }
+    k= 0;
+    for (i = 0; (i < num_attr)&&(k < arraylen); i++)
+    {
+        sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(app_attrs[i].attr_id));
+        k++;
+        sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(app_attrs[i].num_val));
+        k++;
+        sCallbackEnv->SetByteArrayRegion(playerattribs, k, app_attrs[i].num_val,
+                (jbyte*)(app_attrs[i].attr_val));
+        k = k + app_attrs[i].num_val;
+    }
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsetting, addr,
+            playerattribs, (jint)arraylen);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+    sCallbackEnv->DeleteLocalRef(playerattribs);
+}
+
+static void btavrcp_playerapplicationsetting_changed_callback(bt_bdaddr_t *bd_addr,
+                         btrc_player_settings_t *p_vals) {
+
+    jbyteArray addr;
+    jbyteArray playerattribs;
+    int i, k, arraylen;
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+    arraylen = p_vals->num_attr*2;
+    playerattribs = sCallbackEnv->NewByteArray(arraylen);
+    if(!playerattribs)
+    {
+        ALOGE("Fail to new jbyteArray playerattribs ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        sCallbackEnv->DeleteLocalRef(addr);
+        return;
+    }
+    /*
+     * Flatening format: <id,val>
+     */
+    k = 0;
+    for (i = 0; (i < p_vals->num_attr)&&( k < arraylen);i++)
+    {
+        sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_ids[i]));
+        k++;
+        sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_values[i]));
+        k++;
+    }
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsettingchanged, addr,
+            playerattribs, (jint)arraylen);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+    sCallbackEnv->DeleteLocalRef(playerattribs);
+}
+
+static void btavrcp_set_abs_vol_cmd_callback(bt_bdaddr_t *bd_addr, uint8_t abs_vol,
+        uint8_t label) {
+
+    jbyteArray addr;
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
+                                 (jbyte)label);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_register_notification_absvol_callback(bt_bdaddr_t *bd_addr, uint8_t label) {
+    jbyteArray addr;
+
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
+                                 (jbyte)label);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_track_changed_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
+                           btrc_element_attr_val_t *p_attrs) {
+    /*
+     * byteArray will be formatted like this: id,len,string
+     * Assuming text feild to be null terminated.
+     */
+    jbyteArray addr;
+    jintArray attribIds;
+    jobjectArray stringArray;
+    jstring str;
+    jclass strclazz;
+    jint i;
+    ALOGI("%s", __FUNCTION__);
+    if (!checkCallbackThread()) {                                       \
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+        return;                                                         \
+    }
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    attribIds = sCallbackEnv->NewIntArray(num_attr);
+    if(!attribIds) {
+        ALOGE(" failed to set new array for attribIds");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        sCallbackEnv->DeleteLocalRef(addr);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+
+    strclazz = sCallbackEnv->FindClass("java/lang/String");
+    stringArray = sCallbackEnv->NewObjectArray((jint)num_attr, strclazz, 0);
+    if(!stringArray) {
+        ALOGE(" failed to get String array");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        sCallbackEnv->DeleteLocalRef(addr);
+        sCallbackEnv->DeleteLocalRef(attribIds);
+        return;
+    }
+    for(i = 0; i < num_attr; i++)
+    {
+        str = sCallbackEnv->NewStringUTF((char*)(p_attrs[i].text));
+        if(!str) {
+            ALOGE(" Unable to get str ");
+            checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+            sCallbackEnv->DeleteLocalRef(addr);
+            sCallbackEnv->DeleteLocalRef(attribIds);
+            sCallbackEnv->DeleteLocalRef(stringArray);
+            return;
+        }
+        sCallbackEnv->SetIntArrayRegion(attribIds, i, 1, (jint*)&(p_attrs[i].attr_id));
+        sCallbackEnv->SetObjectArrayElement(stringArray, i,str);
+        sCallbackEnv->DeleteLocalRef(str);
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handletrackchanged, addr,
+         (jbyte)(num_attr), attribIds, stringArray);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+    sCallbackEnv->DeleteLocalRef(attribIds);
+    /* TODO check do we need to delete str seperately or not */
+    sCallbackEnv->DeleteLocalRef(stringArray);
+    sCallbackEnv->DeleteLocalRef(strclazz);
+}
+
+static void btavrcp_play_position_changed_callback(bt_bdaddr_t *bd_addr, uint32_t song_len,
+        uint32_t song_pos) {
+
+    jbyteArray addr;
+    ALOGI("%s", __FUNCTION__);
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaypositionchanged, addr,
+         (jint)(song_len), (jint)song_pos);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_play_status_changed_callback(bt_bdaddr_t *bd_addr,
+        btrc_play_status_t play_status) {
+    jbyteArray addr;
+    ALOGI("%s", __FUNCTION__);
+
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if ((!addr)) {
+        ALOGE("Fail to get new array ");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaystatuschanged, addr,
+             (jbyte)play_status);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
 
 static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
     sizeof(sBluetoothAvrcpCallbacks),
     btavrcp_passthrough_response_callback,
-    btavrcp_connection_state_callback
+    btavrcp_groupnavigation_response_callback,
+    btavrcp_connection_state_callback,
+    btavrcp_get_rcfeatures_callback,
+    btavrcp_setplayerapplicationsetting_rsp_callback,
+    btavrcp_playerapplicationsetting_callback,
+    btavrcp_playerapplicationsetting_changed_callback,
+    btavrcp_set_abs_vol_cmd_callback,
+    btavrcp_register_notification_absvol_callback,
+    btavrcp_track_changed_callback,
+    btavrcp_play_position_changed_callback,
+    btavrcp_play_status_changed_callback
 };
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
     method_handlePassthroughRsp =
         env->GetMethodID(clazz, "handlePassthroughRsp", "(II)V");
 
+    method_handleGroupNavigationRsp =
+        env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
+
     method_onConnectionStateChanged =
         env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
 
+    method_getRcFeatures =
+        env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
+
+    method_setplayerappsettingrsp =
+        env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");
+
+    method_handleplayerappsetting =
+        env->GetMethodID(clazz, "handlePlayerAppSetting", "([B[BI)V");
+
+    method_handleplayerappsettingchanged =
+        env->GetMethodID(clazz, "onPlayerAppSettingChanged", "([B[BI)V");
+
+    method_handleSetAbsVolume =
+        env->GetMethodID(clazz, "handleSetAbsVolume", "([BBB)V");
+
+    method_handleRegisterNotificationAbsVol =
+        env->GetMethodID(clazz, "handleRegisterNotificationAbsVol", "([BB)V");
+
+    method_handletrackchanged =
+        env->GetMethodID(clazz, "onTrackChanged", "([BB[I[Ljava/lang/String;)V");
+
+    method_handleplaypositionchanged =
+        env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
+
+    method_handleplaystatuschanged =
+            env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
     ALOGI("%s: succeeds", __FUNCTION__);
 }
 
@@ -183,12 +548,140 @@ static jboolean sendPassThroughCommandNative(JNIEnv *env, jobject object, jbyteA
     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean sendGroupNavigationCommandNative(JNIEnv *env, jobject object, jbyteArray address,
+                                                    jint key_code, jint key_state) {
+    jbyte *addr;
+    bt_status_t status;
+
+    if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+
+    ALOGI("key_code: %d, key_state: %d", key_code, key_state);
+
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return JNI_FALSE;
+    }
+
+    if ((status = sBluetoothAvrcpInterface->send_group_navigation_cmd((bt_bdaddr_t *)addr,
+            (uint8_t)key_code, (uint8_t)key_state))!= BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending Grp Navigation command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void setPlayerApplicationSettingValuesNative(JNIEnv *env, jobject object, jbyteArray address,
+                                                    jbyte num_attrib, jbyteArray attrib_ids,
+                                                    jbyteArray attrib_val) {
+    bt_status_t status;
+    jbyte *addr;
+    uint8_t *pAttrs = NULL;
+    uint8_t *pAttrsVal = NULL;
+    int i;
+    jbyte *attr;
+    jbyte *attr_val;
+
+    if (!sBluetoothAvrcpInterface) return;
+
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    pAttrs = new uint8_t[num_attrib];
+    pAttrsVal = new uint8_t[num_attrib];
+    if ((!pAttrs) ||(!pAttrsVal)) {
+        delete[] pAttrs;
+        ALOGE("setPlayerApplicationSettingValuesNative: not have enough memeory");
+        return;
+    }
+    attr = env->GetByteArrayElements(attrib_ids, NULL);
+    attr_val = env->GetByteArrayElements(attrib_val, NULL);
+    if ((!attr)||(!attr_val)) {
+        delete[] pAttrs;
+        delete[] pAttrsVal;
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    for (i = 0; i < num_attrib; ++i) {
+        pAttrs[i] = (uint8_t)attr[i];
+        pAttrsVal[i] = (uint8_t)attr_val[i];
+    }
+    if (i < num_attrib) {
+        delete[] pAttrs;
+        delete[] pAttrsVal;
+        env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+        env->ReleaseByteArrayElements(attrib_val, attr_val, 0);
+        return;
+    }
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->set_player_app_setting_cmd((bt_bdaddr_t *)addr,
+                                    (uint8_t)num_attrib, pAttrs, pAttrsVal))!= BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending setPlAppSettValNative command, status: %d", status);
+    }
+    delete[] pAttrs;
+    delete[] pAttrsVal;
+    env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+    env->ReleaseByteArrayElements(attrib_val, attr_val, 0);
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void sendAbsVolRspNative(JNIEnv *env, jobject object, jbyteArray address,
+                                jint abs_vol, jint label) {
+    bt_status_t status;
+    jbyte *addr;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->set_volume_rsp((bt_bdaddr_t *)addr,
+                  (uint8_t)abs_vol, (uint8_t)label))!= BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending sendAbsVolRspNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void sendRegisterAbsVolRspNative(JNIEnv *env, jobject object, jbyteArray address,
+                                        jbyte rsp_type, jint abs_vol, jint label) {
+    bt_status_t status;
+    jbyte *addr;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->register_abs_vol_rsp((bt_bdaddr_t *)addr,
+                  (btrc_notification_type_t)rsp_type,(uint8_t)abs_vol, (uint8_t)label))
+                  != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending sendRegisterAbsVolRspNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void *) classInitNative},
     {"initNative", "()V", (void *) initNative},
     {"cleanupNative", "()V", (void *) cleanupNative},
-    {"sendPassThroughCommandNative", "([BII)Z",
-     (void *) sendPassThroughCommandNative},
+    {"sendPassThroughCommandNative", "([BII)Z",(void *) sendPassThroughCommandNative},
+    {"sendGroupNavigationCommandNative", "([BII)Z",(void *) sendGroupNavigationCommandNative},
+    {"setPlayerApplicationSettingValuesNative", "([BB[B[B)V",
+                               (void *) setPlayerApplicationSettingValuesNative},
+    {"sendAbsVolRspNative", "([BII)V",(void *) sendAbsVolRspNative},
+    {"sendRegisterAbsVolRspNative", "([BBII)V",(void *) sendRegisterAbsVolRspNative},
 };
 
 int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
index 5dcec73..8c60fe0 100644 (file)
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothA2dpSink;
+import android.provider.Settings;
 import android.util.Log;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
@@ -143,6 +144,47 @@ public class A2dpSinkService extends ProfileService {
         return mStateMachine.getConnectionState(device);
     }
 
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                       "Need BLUETOOTH_ADMIN permission");
+        Settings.Global.putInt(getContentResolver(),
+            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+            priority);
+        if (DBG) {
+            Log.d(TAG,"Saved priority " + device + " = " + priority);
+        }
+        return true;
+    }
+
+    public int getPriority(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                       "Need BLUETOOTH_ADMIN permission");
+        int priority = Settings.Global.getInt(getContentResolver(),
+            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+            BluetoothProfile.PRIORITY_UNDEFINED);
+        return priority;
+    }
+
+    public void informAvrcpStatePlaying(BluetoothDevice device) {
+        if(mStateMachine != null)
+            mStateMachine.informAvrcpStatePlaying(device);
+    }
+
+    public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode,
+            int keyState) {
+        if(mStateMachine != null)
+            mStateMachine.informAvrcpPassThroughCmd(device, keyCode, keyState);
+    }
+
+    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                       "Need BLUETOOTH permission");
+        if (DBG) {
+            Log.d(TAG, "isA2dpPlaying(" + device + ")");
+        }
+        return mStateMachine.isPlaying(device);
+    }
+
     BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mStateMachine.getAudioConfig(device);
@@ -204,6 +246,24 @@ public class A2dpSinkService extends ProfileService {
             return service.getConnectionState(device);
         }
 
+        public boolean isA2dpPlaying(BluetoothDevice device) {
+            A2dpSinkService service = getService();
+            if (service == null) return false;
+            return service.isA2dpPlaying(device);
+        }
+
+        public boolean setPriority(BluetoothDevice device, int priority) {
+            A2dpSinkService service = getService();
+            if (service == null) return false;
+            return service.setPriority(device, priority);
+        }
+
+        public int getPriority(BluetoothDevice device) {
+            A2dpSinkService service = getService();
+            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            return service.getPriority(device);
+        }
+
         public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
             A2dpSinkService service = getService();
             if (service == null) return null;
index d57a0ca..29b8bc8 100644 (file)
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.AudioDevicePort;
+import android.media.AudioPatch;
+import android.media.AudioSystem;
+import android.media.AudioPortConfig;
+import android.media.AudioPort;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
@@ -52,6 +60,7 @@ import android.util.Log;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.avrcp.AvrcpControllerService;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -61,13 +70,21 @@ import java.util.HashMap;
 import java.util.Set;
 
 final class A2dpSinkStateMachine extends StateMachine {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     static final int CONNECT = 1;
     static final int DISCONNECT = 2;
+    final private static int EVENT_TYPE_REQUEST_AUDIO_FOCUS = 4;
     private static final int STACK_EVENT = 101;
     private static final int CONNECT_TIMEOUT = 201;
 
+    private static final int IS_INVALID_DEVICE = 0;
+    private static final int IS_VALID_DEVICE = 1;
+    public static final int AVRC_ID_PLAY = 0x44;
+    public static final int AVRC_ID_PAUSE = 0x46;
+    public static final int KEY_STATE_PRESSED = 0;
+    public static final int KEY_STATE_RELEASED = 1;
+
     private Disconnected mDisconnected;
     private Pending mPending;
     private Connected mConnected;
@@ -81,6 +98,29 @@ final class A2dpSinkStateMachine extends StateMachine {
 
     private static final int MSG_CONNECTION_STATE_CHANGED = 0;
 
+    private static final int AUDIO_FOCUS_LOSS = 0;
+    private static final int AUDIO_FOCUS_GAIN = 1;
+    private static final int AUDIO_FOCUS_LOSS_TRANSIENT = 2;
+    private static final int AUDIO_FOCUS_LOSS_CAN_DUCK = 3;
+    private static final int AUDIO_FOCUS_REQUEST_MESSAGE_DELAYED = 500;
+    private static final boolean USE_AUDIOTRACK = true;
+    private static final boolean BLOCK_REMOTE_INITIATED_STREAMING = true;
+    private static boolean mAvrcpPlaySent = false;
+
+    private int mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+
+     /* Used to indicate focus lost */
+    private static final int STATE_FOCUS_LOST = 0;
+    /* Used to inform bluedroid about AVRCP State changes */
+    private static final int STATE_FOCUS_READY = 1;
+    /* Used to inform bluedroid that focus is granted */
+    private static final int STATE_FOCUS_GRANTED = 3;
+
+    private final Object mLockForPatch = new Object();
+    private AudioDevicePort mInPortA2dpSink;
+    private AudioDevicePort mOutPortSpeaker;
+    private AudioPatch mA2dpSinkAudioPatch;
+
     // mCurrentDevice is the device connected before the state changes
     // mTargetDevice is the device to be connected
     // mIncomingDevice is the device connecting to us, valid only in Pending state
@@ -106,6 +146,7 @@ final class A2dpSinkStateMachine extends StateMachine {
     private BluetoothDevice mCurrentDevice = null;
     private BluetoothDevice mTargetDevice = null;
     private BluetoothDevice mIncomingDevice = null;
+    private BluetoothDevice mPlayingDevice = null;
 
     private final HashMap<BluetoothDevice,BluetoothAudioConfig> mAudioConfigs
             = new HashMap<BluetoothDevice,BluetoothAudioConfig>();
@@ -138,8 +179,112 @@ final class A2dpSinkStateMachine extends StateMachine {
         mIntentBroadcastHandler = new IntentBroadcastHandler();
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        if(!USE_AUDIOTRACK)
+            mAudioManager.registerAudioPortUpdateListener(mAudioPortListener);
+    }
+    private void releasePatch() {
+        if(USE_AUDIOTRACK) {
+            log(" releasePatch: AudioTrack, inform focus loss");
+            informAudioFocusStateNative(STATE_FOCUS_LOST);
+            return;
+        }
+        synchronized (mLockForPatch){
+            log("releaseAudioPatch AudioPatch = " + mA2dpSinkAudioPatch);
+            if(mA2dpSinkAudioPatch != null) {
+                mAudioManager.releaseAudioPatch(mA2dpSinkAudioPatch);
+                mA2dpSinkAudioPatch = null;
+            }
+        }
+    }
+    private void patchPorts() {
+        /* If we are using AudioTrack, we need to broadcast and inform bluedroid from here */
+        if(USE_AUDIOTRACK){
+            log(" patchPorts: AudioTrack, inform focus gain");
+            broadcastAudioState(mPlayingDevice, BluetoothA2dpSink.STATE_PLAYING,
+                    BluetoothA2dpSink.STATE_NOT_PLAYING);
+            informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+            return;
+        }
+        synchronized (mLockForPatch) {
+            log("patchPorts : mA2dpSinkAudioPatch: " + mA2dpSinkAudioPatch +
+                " mPlayingDevice " + mPlayingDevice + "mOutPortSpeaker" + mOutPortSpeaker);
+            if((mA2dpSinkAudioPatch == null) && (mPlayingDevice != null) &&
+               (mOutPortSpeaker != null) && (mInPortA2dpSink != null)) {
+                if((mAudioConfigs == null)||(!mAudioConfigs.containsKey(mPlayingDevice))) {
+                    log(" AudioConfigs not yet received, returning");
+                    return;
+                }
+                int sampleRate = getAudioConfig(mPlayingDevice).getSampleRate();
+                int channelMask = getAudioConfig(mPlayingDevice).getChannelConfig();
+                int format = getAudioConfig(mPlayingDevice).getAudioFormat();
+
+                AudioPortConfig sourcePortArray[] =
+                    {mInPortA2dpSink.buildConfig(sampleRate, channelMask, format, null)};
+                AudioPortConfig sinkPortArray[] =
+                    {mOutPortSpeaker.buildConfig(sampleRate, channelMask, format, null)};
+                AudioPatch patchPortArray[] = {null};
+                /*  broadCast Audio State */
+                broadcastAudioState(mPlayingDevice, BluetoothA2dpSink.STATE_PLAYING,
+                                                 BluetoothA2dpSink.STATE_NOT_PLAYING);
+
+                int ret = mAudioManager.createAudioPatch(patchPortArray, sourcePortArray,
+                                                                                   sinkPortArray);
+                if (ret == 0) {
+                    mA2dpSinkAudioPatch = patchPortArray[0];
+                    log("PatchCreated success: " + ret + " mA2dpSinkAudioPatch: "
+                                                                         + mA2dpSinkAudioPatch);
+                } else {
+                    log("PatchCreated failed returned: " + ret);
+                }
+            }
+        }
     }
 
+    private final AudioManager.OnAudioPortUpdateListener mAudioPortListener =
+                       new AudioManager.OnAudioPortUpdateListener(){
+        public void onAudioPortListUpdate(AudioPort[] portList) {
+            synchronized (mLockForPatch){
+                log("onAudioPortListUpdate");
+                mOutPortSpeaker = null;
+                mInPortA2dpSink = null;
+
+                for (int i = 0; i < portList.length; i++) {
+                    AudioPort port = portList[i];
+                    if(port instanceof AudioDevicePort) {
+                        AudioDevicePort devicePort = (AudioDevicePort)port;
+                        if(devicePort.type() == AudioSystem.DEVICE_OUT_SPEAKER) {
+                            log("Updating Speaker Port");
+                            mOutPortSpeaker = devicePort;
+                        } else if(devicePort.type() == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+                            log("Updating In Port A2DP Sink");
+                            mInPortA2dpSink = devicePort;
+                            /* Check if we still have focus */
+                            if ((mAudioFocusAcquired == AUDIO_FOCUS_GAIN) &&
+                                (mInPortA2dpSink != null) && (mA2dpSinkAudioPatch == null)) {
+                                /* This is the case of Port available
+                                 * later than focus acquired. Try patching ports now
+                                 */
+                                 log(" Sink Port updated, but patch not made");
+                                 patchPorts();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        public void onAudioPatchListUpdate(AudioPatch[] patchList){
+            log("onAudioPatchListUpdate");
+            for(int i = 0; i < patchList.length; i++) {
+                log("Patch List " + i +" : "+ patchList[i]);
+            }
+        }
+
+        public void onServiceDied() {
+            log(" Service Died");
+        }
+    };
+
     static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
         Log.d("A2dpSinkStateMachine", "make");
         A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
@@ -152,8 +297,18 @@ final class A2dpSinkStateMachine extends StateMachine {
     }
 
     public void cleanup() {
+        /*
+         * remove cleanup of resources
+         * not acquired in AudioTrack approach
+         */
+        if(!USE_AUDIOTRACK)
+            releasePatch();
         cleanupNative();
         mAudioConfigs.clear();
+        if(USE_AUDIOTRACK) return;
+        if (mContext != null)
+            mContext.unregisterReceiver(mA2dpReceiver);
+        mAudioManager.unregisterAudioPortUpdateListener(mAudioPortListener);
     }
 
     public void dump(StringBuilder sb) {
@@ -167,6 +322,11 @@ final class A2dpSinkStateMachine extends StateMachine {
         @Override
         public void enter() {
             log("Enter Disconnected: " + getCurrentMessage().what);
+            /*
+             * Remove audio focus request when not in connected state
+             */
+            removeMessages(EVENT_TYPE_REQUEST_AUDIO_FOCUS);
+            mAvrcpPlaySent = false;
         }
 
         @Override
@@ -289,6 +449,11 @@ final class A2dpSinkStateMachine extends StateMachine {
         @Override
         public void enter() {
             log("Enter Pending: " + getCurrentMessage().what);
+            /*
+             * Remove audio focus request when not in connected state
+             */
+            removeMessages(EVENT_TYPE_REQUEST_AUDIO_FOCUS);
+            mAvrcpPlaySent = false;
         }
 
         @Override
@@ -477,6 +642,7 @@ final class A2dpSinkStateMachine extends StateMachine {
             // Upon connected, the audio starts out as stopped
             broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
                                 BluetoothA2dpSink.STATE_PLAYING);
+            mAvrcpPlaySent = false;
         }
 
         @Override
@@ -523,9 +689,22 @@ final class A2dpSinkStateMachine extends StateMachine {
                                        BluetoothProfile.STATE_DISCONNECTED);
                         break;
                     }
+
+                    if (mAudioFocusAcquired != AUDIO_FOCUS_LOSS) {
+                        releasePatch();
+                        int status = mAudioManager.abandonAudioFocus(mAudioFocusListener);
+                        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                            mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+                        }
+                     }
+                    mPlayingDevice = null;
+
                     transitionTo(mPending);
                 }
                     break;
+                case EVENT_TYPE_REQUEST_AUDIO_FOCUS:
+                    processAudioFocusRequestEvent(1, (BluetoothDevice) message.obj);
+                    break;
                 case STACK_EVENT:
                     StackEvent event = (StackEvent) message.obj;
                     switch (event.type) {
@@ -554,6 +733,9 @@ final class A2dpSinkStateMachine extends StateMachine {
             switch (state) {
                 case CONNECTION_STATE_DISCONNECTED:
                     mAudioConfigs.remove(device);
+                    if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
+                        mPlayingDevice = null;
+                    }
                     if (mCurrentDevice.equals(device)) {
                         broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
                                                  BluetoothProfile.STATE_CONNECTED);
@@ -561,6 +743,14 @@ final class A2dpSinkStateMachine extends StateMachine {
                             mCurrentDevice = null;
                             transitionTo(mDisconnected);
                         }
+                        releasePatch();
+                        int status = mAudioManager.abandonAudioFocus(mAudioFocusListener);
+                        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                             mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+                        /* Check if we need to release patch here
+                         * Would we ever get disconnect directly without
+                         * Suspend or Stoppped being called before */
+                        }
                     } else {
                         loge("Disconnected from unknown device: " + device);
                     }
@@ -576,24 +766,89 @@ final class A2dpSinkStateMachine extends StateMachine {
                                                            mCurrentDevice);
                 return;
             }
+            log(" processAudioStateEvent in state " + state);
             switch (state) {
                 case AUDIO_STATE_STARTED:
-                    broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
-                                        BluetoothA2dpSink.STATE_NOT_PLAYING);
+                    if (mPlayingDevice == null) {
+                        mPlayingDevice = device;
+                    }
+                    if(!USE_AUDIOTRACK)
+                        requestAudioFocus(true, device, 0);
                     break;
                 case AUDIO_STATE_REMOTE_SUSPEND:
                 case AUDIO_STATE_STOPPED:
+                    mPlayingDevice = null;
+                    releasePatch();
                     broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
                                         BluetoothA2dpSink.STATE_PLAYING);
+                    if (mAudioFocusAcquired == AUDIO_FOCUS_LOSS) {
+                       /* Audio Focus was already lost, no need to do it again */
+                       return;
+                    }
+                    if ((mAudioFocusAcquired == AUDIO_FOCUS_LOSS_TRANSIENT) &&
+                                     (state == AUDIO_STATE_REMOTE_SUSPEND)) {
+                        log(" Dont't Loose audiofocus in case of suspend ");
+                        break;
+                    }
+                    int status = mAudioManager.abandonAudioFocus(mAudioFocusListener);
+                    if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                        mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+                    }
                     break;
                 default:
                   loge("Audio State Device: " + device + " bad state: " + state);
                   break;
             }
         }
+
+        private void processAudioFocusRequestEvent(int enable, BluetoothDevice device) {
+            if ((mCurrentDevice != null) && (mCurrentDevice.equals(device))
+                    && (1 == enable)) {
+
+                if (BLOCK_REMOTE_INITIATED_STREAMING) {
+                    /*
+                     * If play command is not send from UI
+                     * don't rquest focus.
+                     */
+                    if (!mAvrcpPlaySent) {
+                        log(" AVRCP Play not sent from UI, send Pause");
+                        SendPassThruPause(device);
+                        if(USE_AUDIOTRACK)
+                            informAudioFocusStateNative(STATE_FOCUS_LOST);
+                        /*
+                         * TODO, any additional intent that we want to send
+                         */
+                        return;
+                    }
+                }
+                if (mAudioFocusAcquired == AUDIO_FOCUS_LOSS_TRANSIENT) {
+                    log(" Transient Loss Still, Don't acquire focus, Send PAUSE");
+                    SendPassThruPause(device);
+                    if(USE_AUDIOTRACK)
+                        informAudioFocusStateNative(STATE_FOCUS_LOST);
+                    return;
+                }
+                else if (mAudioFocusAcquired == AUDIO_FOCUS_GAIN) {
+                    patchPorts();
+                    return; /* if we already have focus, don't request again */
+                }
+                int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
+                                  AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
+                log(" Audio Focus Request returned " + status);
+                if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                    mAudioFocusAcquired = AUDIO_FOCUS_GAIN;
+                    patchPorts();
+                }
+                else {
+                    log("Can't acquire Focus, request with delay");
+                    requestAudioFocus(true, device, AUDIO_FOCUS_REQUEST_MESSAGE_DELAYED);
+                }
+            }
+        }
     }
 
     private void processAudioConfigEvent(BluetoothAudioConfig audioConfig, BluetoothDevice device) {
+        log("processAudioConfigEvent: " + device);
         mAudioConfigs.put(device, audioConfig);
         broadcastAudioConfig(device, audioConfig);
     }
@@ -644,6 +899,59 @@ final class A2dpSinkStateMachine extends StateMachine {
         return devices;
     }
 
+    boolean isPlaying(BluetoothDevice device) {
+        synchronized(this) {
+            if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void informAvrcpStatePlaying(BluetoothDevice device) {
+        if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
+            log(" AVRCP State is Playing");
+            /* If call is ongoing, Focus already acquired, no need to
+             * send this info to bluedroid
+             */
+            /*
+             * If Flag is set, this info should not be used
+             * to request focus.
+             */
+            if (BLOCK_REMOTE_INITIATED_STREAMING)
+                return;
+            if(mAudioFocusAcquired == AUDIO_FOCUS_LOSS_TRANSIENT)
+                SendPassThruPause(device);
+            if((USE_AUDIOTRACK) && (mAudioFocusAcquired == AUDIO_FOCUS_LOSS))
+                informAudioFocusStateNative(STATE_FOCUS_READY);
+        }
+    }
+    void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode,
+            int keyState) {
+        if (!BLOCK_REMOTE_INITIATED_STREAMING)
+            return;
+        log(" informAvrcpPassThroughCmd device: " + device + "  key: " + keyCode
+               + " keyState: " + keyState);
+        if ((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY) &&
+            (keyState == BluetoothAvrcpController.KEY_STATE_RELEASED)) {
+            mAvrcpPlaySent = true;
+            /*
+             * SO that if packets are received, we can request for focus
+             * from bluedroid
+             */
+            informAudioFocusStateNative(STATE_FOCUS_READY);
+        }
+        if (((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE)||
+            (keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP)) &&
+             (keyState == BluetoothAvrcpController.KEY_STATE_RELEASED)) {
+            mAvrcpPlaySent = false;
+            /*
+             * emulate a remote initiaited suspend.
+             */
+            onAudioStateChanged(AUDIO_STATE_REMOTE_SUSPEND,
+                    getByteAddress(device));
+        }
+    }
     boolean okToConnect(BluetoothDevice device) {
         AdapterService adapterService = AdapterService.getAdapterService();
         boolean ret = true;
@@ -680,7 +988,9 @@ final class A2dpSinkStateMachine extends StateMachine {
     // This method does not check for error conditon (newState == prevState)
     private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
 
-        int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
+       int delay = 0;
+       if(!USE_AUDIOTRACK)
+           delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
                 BluetoothProfile.A2DP_SINK);
 
         mWakeLock.acquire();
@@ -741,6 +1051,27 @@ final class A2dpSinkStateMachine extends StateMachine {
         sendMessage(STACK_EVENT, event);
     }
 
+    private void onAudioFocusRequested(byte[] address) {
+        BluetoothDevice device = getDevice(address);
+        if (BLOCK_REMOTE_INITIATED_STREAMING) {
+            /*
+             * IN this case we mPlayingDevice can be null
+             */
+            requestAudioFocus(true, device, 0);
+        }
+        else if(device.equals(mPlayingDevice))
+            requestAudioFocus(true, device, 0);
+    }
+
+    private void requestAudioFocus(boolean enable, BluetoothDevice device, int delay) {
+        log(" requestAudioFocus for  " + device + " enable " + enable);
+        if (enable) {
+            // send a request for audio_focus
+            Message posMsg = obtainMessage(EVENT_TYPE_REQUEST_AUDIO_FOCUS, device);
+            sendMessageDelayed(posMsg, delay);
+        }
+    }
+
     private BluetoothDevice getDevice(byte[] address) {
         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
     }
@@ -781,6 +1112,142 @@ final class A2dpSinkStateMachine extends StateMachine {
         }
     }
 
+    public boolean SendPassThruPlay(BluetoothDevice mDevice) {
+            log("SendPassThruPlay + ");
+            AvrcpControllerService avrcpCtrlService = AvrcpControllerService.getAvrcpControllerService();
+            if ((avrcpCtrlService != null) && (mDevice != null) &&
+                (avrcpCtrlService.getConnectedDevices().contains(mDevice))){
+                avrcpCtrlService.sendPassThroughCmd(mDevice,
+                    BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+                    BluetoothAvrcpController.KEY_STATE_PRESSED);
+                avrcpCtrlService.sendPassThroughCmd(mDevice,
+                    BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+                    BluetoothAvrcpController.KEY_STATE_RELEASED);
+                log(" SendPassThruPlay command sent - ");
+                return true;
+            } else {
+                log("passthru command not sent, connection unavailable");
+                return false;
+            }
+        }
+
+    public boolean SendPassThruPause(BluetoothDevice mDevice) {
+        log("SendPassThruPause + ");
+        AvrcpControllerService avrcpCtrlService = AvrcpControllerService.getAvrcpControllerService();
+        if ((avrcpCtrlService != null) && (mDevice != null) &&
+            (avrcpCtrlService.getConnectedDevices().contains(mDevice))){
+
+            avrcpCtrlService.sendPassThroughCmd(mDevice,
+                BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
+                BluetoothAvrcpController.KEY_STATE_PRESSED);
+            avrcpCtrlService.sendPassThroughCmd(mDevice,
+                BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
+                BluetoothAvrcpController.KEY_STATE_RELEASED);
+            log(" SendPassThruPause command sent - ");
+            return true;
+        } else {
+            log("passthru command not sent, connection unavailable");
+            return false;
+        }
+    }
+
+    private final BroadcastReceiver mA2dpReceiver = new BroadcastReceiver() {
+        @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                log("onReceive  " + action);
+                if (action.equals("com.android.music.musicservicecommand")) {
+                    String cmd = intent.getStringExtra("command");
+                    log("Command Received  " + cmd);
+                    if (cmd.equals("pause")) {
+                        if (mCurrentDevice != null) {
+                            if (SendPassThruPause(mCurrentDevice)) {
+                                log(" Sending AVRCP Pause");
+                            } else {
+                                log(" Sending Disconnect AVRCP Not Up");
+                                disconnectA2dpNative(getByteAddress(mCurrentDevice));
+                            }
+                            releasePatch();
+                            if (mAudioFocusAcquired != AUDIO_FOCUS_LOSS) {
+                                int status = mAudioManager.abandonAudioFocus(mAudioFocusListener);
+                                log("abandonAudioFocus returned" + status);
+                                if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                                    mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
+        public void onAudioFocusChange(int focusChange){
+            log("onAudioFocusChangeListener focuschange " + focusChange);
+            switch(focusChange){
+                case AudioManager.AUDIOFOCUS_LOSS:
+                    if (mCurrentDevice != null) {
+                        /* First Release the Patch Anyways */
+                        if (SendPassThruPause(mCurrentDevice)) {
+                            log(" Sending AVRCP Pause");
+                        } else {
+                            log(" Sending Disconnect AVRCP Not Up");
+                            disconnectA2dpNative(getByteAddress(mCurrentDevice));
+                        }
+                        if(USE_AUDIOTRACK)
+                            informAudioFocusStateNative(STATE_FOCUS_LOST);
+                        int status = mAudioManager.abandonAudioFocus(mAudioFocusListener);
+                        log("abandonAudioFocus returned" + status);
+                        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                            mAudioFocusAcquired = AUDIO_FOCUS_LOSS;
+                        }
+                    }
+                    break;
+                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+                    if ((mCurrentDevice != null) && (getCurrentState() == mConnected)) {
+                    /* don't abandon focus, but fake focus loss */
+                       mAudioFocusAcquired = AUDIO_FOCUS_LOSS_TRANSIENT;
+                       if (SendPassThruPause(mCurrentDevice)) {
+                            log(" Sending AVRCP Pause");
+                        } else {
+                            log(" AVRCP Connection not UP");
+                            disconnectA2dpNative(getByteAddress(mCurrentDevice));
+                            /* TODO what shld we do in case AVRCP connection is not there */
+                        }
+                       if(USE_AUDIOTRACK)
+                           informAudioFocusStateNative(STATE_FOCUS_LOST);
+                    }
+                    break;
+                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                    log(" Received AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK ");
+                    mAudioFocusAcquired = AUDIO_FOCUS_LOSS_CAN_DUCK;
+                    // TODO(sanketa): Currently we do not honor this and keep playing music at the
+                    // previous volume. This should be changed to implement ducking.
+                    break;
+                case AudioManager.AUDIOFOCUS_GAIN:
+                    // we got focus gain
+                    if ((mCurrentDevice != null) && (getCurrentState() == mConnected)) {
+                        if (mAudioFocusAcquired == AUDIO_FOCUS_LOSS_CAN_DUCK) {
+                            log(" Received Can_Duck earlier, Ignore Now ");
+                            mAudioFocusAcquired = AUDIO_FOCUS_GAIN;
+                            break;
+                        }
+                        mAudioFocusAcquired = AUDIO_FOCUS_GAIN;
+                        if (SendPassThruPlay(mCurrentDevice)) {
+                            log(" Sending AVRCP Play");
+                        } else {
+                            log(" AVRCP Connection not up");
+                            /* TODO what shld we do in case AVRCP connection is not there */
+                        }
+                        if(USE_AUDIOTRACK)
+                            informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
 
     // Event types for STACK_EVENT message
     final private static int EVENT_TYPE_NONE = 0;
@@ -806,4 +1273,5 @@ final class A2dpSinkStateMachine extends StateMachine {
     private native void cleanupNative();
     private native boolean connectA2dpNative(byte[] address);
     private native boolean disconnectA2dpNative(byte[] address);
+    private native void informAudioFocusStateNative(int focusGranted);
 }
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java b/src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java
new file mode 100644 (file)
index 0000000..422bcfe
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import com.android.bluetooth.Utils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.HashMap;
+import android.util.Log;
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
+import android.media.session.PlaybackState;
+import android.media.MediaMetadata;
+/**
+ * Provides helper classes used by other AvrcpControllerClasses.
+ */
+class AvrcpUtils {
+
+    private static final String TAG = "AvrcpUtils";
+    /*
+     * First 2 apis are utility functions to converts values from AvrcpPlayerSettings defined
+     * in BluetoothAvrcpPlayerSettings to BT spec defined Id and Vals.
+     */
+    public static int mapAttribIdValtoAvrcpPlayerSetting( byte attribId, byte attribVal) {
+        if(AvrcpControllerConstants.VDBG) Log.d(TAG, "attribId: " + attribId + " attribVal: " + attribVal);
+        if (attribId == AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS) {
+            switch(attribVal) {
+            case AvrcpControllerConstants.EQUALIZER_STATUS_OFF:
+                return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            case AvrcpControllerConstants.EQUALIZER_STATUS_ON:
+                return BluetoothAvrcpPlayerSettings.STATE_ON;
+            }
+        }
+        else if (attribId == AvrcpControllerConstants.ATTRIB_REPEAT_STATUS) {
+            switch(attribVal) {
+            case AvrcpControllerConstants.REPEAT_STATUS_ALL_TRACK_REPEAT:
+                return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+            case AvrcpControllerConstants.REPEAT_STATUS_GROUP_REPEAT:
+                return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+            case AvrcpControllerConstants.REPEAT_STATUS_OFF:
+                return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            case AvrcpControllerConstants.REPEAT_STATUS_SINGLE_TRACK_REPEAT:
+                return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+            }
+        }
+        else if (attribId == AvrcpControllerConstants.ATTRIB_SCAN_STATUS) {
+            switch(attribVal) {
+            case AvrcpControllerConstants.SCAN_STATUS_ALL_TRACK_SCAN:
+                return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+            case AvrcpControllerConstants.SCAN_STATUS_GROUP_SCAN:
+                return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+            case AvrcpControllerConstants.SCAN_STATUS_OFF:
+                return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            }
+        }
+        else if (attribId == AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS) {
+            switch(attribVal) {
+            case AvrcpControllerConstants.SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
+                return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+            case AvrcpControllerConstants.SHUFFLE_STATUS_GROUP_SHUFFLE:
+                return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+            case AvrcpControllerConstants.SHUFFLE_STATUS_OFF:
+                return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            }
+        }
+        return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+    }
+    public static int mapAvrcpPlayerSettingstoBTAttribVal(int mSetting, int mSettingVal) {
+        if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
+            switch(mSettingVal) {
+            case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                return AvrcpControllerConstants.EQUALIZER_STATUS_OFF;
+            case BluetoothAvrcpPlayerSettings.STATE_ON:
+                return AvrcpControllerConstants.EQUALIZER_STATUS_ON;
+            }
+        }
+        else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
+            switch(mSettingVal) {
+            case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                return AvrcpControllerConstants.REPEAT_STATUS_OFF;
+            case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
+                return AvrcpControllerConstants.REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+            case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                return AvrcpControllerConstants.REPEAT_STATUS_ALL_TRACK_REPEAT;
+            case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                return AvrcpControllerConstants.REPEAT_STATUS_GROUP_REPEAT;
+            }
+        }
+        else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
+            switch(mSettingVal) {
+            case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                return AvrcpControllerConstants.SHUFFLE_STATUS_OFF;
+            case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                return AvrcpControllerConstants.SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+            case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                return AvrcpControllerConstants.SHUFFLE_STATUS_GROUP_SHUFFLE;
+            }
+        }
+        else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
+            switch(mSettingVal) {
+            case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                return AvrcpControllerConstants.SCAN_STATUS_OFF;
+            case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                return AvrcpControllerConstants.SCAN_STATUS_ALL_TRACK_SCAN;
+            case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                return AvrcpControllerConstants.SCAN_STATUS_GROUP_SCAN;
+            }
+        }
+        return AvrcpControllerConstants.STATUS_INVALID;
+    }
+    /*
+     * This api converts btPlayStatus to PlaybackState
+     */
+    public static PlaybackState mapBtPlayStatustoPlayBackState(byte btPlayStatus, long btPlayPos) {
+        int mState = PlaybackState.STATE_NONE;
+        long position = btPlayPos;
+        float speed = 1;
+        switch(btPlayStatus) {
+            case AvrcpControllerConstants.PLAY_STATUS_STOPPED:
+                mState = PlaybackState.STATE_STOPPED;
+                position = 0;
+                speed = 0;
+            break;
+            case AvrcpControllerConstants.PLAY_STATUS_PLAYING:
+                mState = PlaybackState.STATE_PLAYING;
+            break;
+            case AvrcpControllerConstants.PLAY_STATUS_PAUSED:
+                mState = PlaybackState.STATE_PAUSED;
+                speed = 0;
+            break;
+            case AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK:
+                mState = PlaybackState.STATE_FAST_FORWARDING;
+                speed = 3;
+            break;
+            case AvrcpControllerConstants.PLAY_STATUS_REV_SEEK:
+                mState = PlaybackState.STATE_REWINDING;
+                speed = -3;
+            break;
+        }
+        return new PlaybackState.Builder().setState(mState, position, speed).build();
+    }
+    /*
+     * This api converts meta info into MediaMetaData
+     */
+    public static MediaMetadata getMediaMetaData(TrackInfo mTrackInfo) {
+        if(AvrcpControllerConstants.VDBG) Log.d(TAG, " TrackInfo " + mTrackInfo.toString());
+        MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,
+                mTrackInfo.mArtistName);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,
+                mTrackInfo.mTrackTitle);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM,
+                mTrackInfo.mAlbumTitle);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE,
+                mTrackInfo.mGenre);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+                mTrackInfo.mTrackNum);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+                mTrackInfo.mTotalTracks);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+                mTrackInfo.mTrackLen);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
+                String.valueOf(mTrackInfo.mItemUid));
+        return mMetaDataBuilder.build();
+    }
+    /*
+     * Display Apis
+     */
+    public static String displayMetaData(MediaMetadata mMetaData) {
+        StringBuffer sb = new StringBuffer();
+        /* this will only show artist, title and album */
+        sb.append(mMetaData.getDescription().toString() + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_GENRE))
+            sb.append(mMetaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID))
+            sb.append(mMetaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
+            sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+        if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+        return sb.toString();
+    }
+    public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
+        StringBuffer sb =  new StringBuffer();
+        int supportedSetting = mSett.getSettings();
+        if(AvrcpControllerConstants.VDBG) Log.d(TAG," setting: " + supportedSetting);
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+            sb.append(" EQ : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_EQUALIZER)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+            sb.append(" REPEAT : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_REPEAT)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+            sb.append(" SHUFFLE : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_SHUFFLE)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+            sb.append(" SCAN : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_SCAN)));
+        }
+        return sb.toString();
+    }
+}
+/*
+ * Contains information about remote device
+ */
+class RemoteDevice {
+    BluetoothDevice mBTDevice;
+    int mRemoteFeatures;
+    int mBatteryStatus;
+    int mSystemStatus;
+    int mAbsVolNotificationState;
+    int mNotificationLabel;
+    boolean mFirstAbsVolCmdRecvd;
+
+    public void cleanup() {
+        mBTDevice = null;
+        mRemoteFeatures = AvrcpControllerConstants.BTRC_FEAT_NONE;
+        mBatteryStatus = AvrcpControllerConstants.BATT_POWER_UNDEFINED;
+        mSystemStatus = AvrcpControllerConstants.SYSTEM_STATUS_UNDEFINED;
+        mAbsVolNotificationState = AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+        mNotificationLabel = AvrcpControllerConstants.VOLUME_LABEL_UNDEFINED;
+        mFirstAbsVolCmdRecvd = false;
+    }
+
+    public RemoteDevice(BluetoothDevice mDevice) {
+        mBTDevice = mDevice;
+        mRemoteFeatures = AvrcpControllerConstants.BTRC_FEAT_NONE;
+        mBatteryStatus = AvrcpControllerConstants.BATT_POWER_UNDEFINED;
+        mSystemStatus = AvrcpControllerConstants.SYSTEM_STATUS_UNDEFINED;
+        mAbsVolNotificationState = AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+        mNotificationLabel = AvrcpControllerConstants.VOLUME_LABEL_UNDEFINED;
+        mFirstAbsVolCmdRecvd = false;
+    }
+
+    public boolean isBrowsingSupported() {
+        if((mRemoteFeatures & AvrcpControllerConstants.BTRC_FEAT_BROWSE) != 0)
+            return true;
+        else
+           return false;
+    }
+    public boolean isMetaDataSupported() {
+        if((mRemoteFeatures & AvrcpControllerConstants.BTRC_FEAT_METADATA) != 0)
+            return true;
+        else
+           return false;
+    }
+}
+
+/*
+ * Base Class for Media Item
+ */
+class MediaItem {
+    /*
+     * This is a combination of locaiton and item. Spec Snippet
+     * In VFS if same item is in different location it may have same uid.
+     * In Now Playing same item should have differnt UID
+     * Can never be 0, used only for GetElementAttributes
+     * TODO: UID counter, which is used for database aware player
+     */
+    double mItemUid;
+}
+
+/*
+ * Contains information Player Application Setting
+ */
+class PlayerApplicationSettings {
+    public byte attr_Id;
+    public byte attr_val;
+    public byte [] supported_values;
+    public String attr_text;
+    public String [] supported_values_text;// This is to keep displayable text in UTF-8
+}
+/*
+ * Contains information about remote player
+ */
+class PlayerInfo {
+    private static final String TAG = "PlayerInfo";
+    byte mPlayStatus;
+    long mPlayTime;
+    /*
+     * 2 byte player id to identify player.
+     * In 1.3 this value will be set to zero
+     */
+    char mPlayerId;
+    ArrayList<PlayerApplicationSettings> mPlayerAppSetting;
+    private void resetPlayer() {
+        mPlayStatus = AvrcpControllerConstants.PLAY_STATUS_STOPPED;
+        mPlayTime   = AvrcpControllerConstants.PLAYING_TIME_INVALID;
+        mPlayerId   = 0;
+        mPlayerAppSetting = new ArrayList<PlayerApplicationSettings>();
+    }
+    public PlayerInfo() {
+        resetPlayer();
+    }
+    public void setSupportedPlayerAppSetting (ByteBuffer bb) {
+        /* ByteBuffer has to be of the following format
+         * id, num_values, values[]
+         */
+        while(bb.hasRemaining()) {
+            PlayerApplicationSettings plAppSetting = new PlayerApplicationSettings();
+            plAppSetting.attr_Id = bb.get();
+            byte numSupportedVals = bb.get();
+            plAppSetting.supported_values = new byte[numSupportedVals];
+            for (int i = 0; i<numSupportedVals; i++) {
+                plAppSetting.supported_values[i] = bb.get();
+            }
+            mPlayerAppSetting.add(plAppSetting);
+        }
+    }
+    public void updatePlayerAppSetting(ByteBuffer bb) {
+        /* ByteBuffer has to be of the following format
+         * <id, value>
+         */
+        if(mPlayerAppSetting.isEmpty())
+            return;
+        while(bb.hasRemaining()) {
+            byte attribId = bb.get();
+            for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+                if(plAppSetting.attr_Id == attribId)
+                    plAppSetting.attr_val = bb.get();
+            }
+        }
+    }
+
+    public BluetoothAvrcpPlayerSettings getSupportedPlayerAppSetting() {
+        /*
+         * Here we create PlayerAppSetting
+         * based on BluetoothAvrcpPlayerSettings
+         */
+        int supportedSettings = 0; // Player App Setting used by BluetoothAvrcpPlayerSettings.
+        for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+            switch(plAppSetting.attr_Id) {
+            case AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS:
+                supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
+                break;
+            case AvrcpControllerConstants.ATTRIB_REPEAT_STATUS:
+                supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
+                break;
+            case AvrcpControllerConstants.ATTRIB_SCAN_STATUS:
+                supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_SCAN;
+                break;
+            case AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS:
+                supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
+                break;
+            }
+        }
+        BluetoothAvrcpPlayerSettings mAvrcpPlayerAppSetting = new
+                                  BluetoothAvrcpPlayerSettings(supportedSettings);
+        for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+            switch(plAppSetting.attr_Id) {
+            case AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS:
+                mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER,
+                           AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+                                plAppSetting.attr_val));
+                break;
+            case AvrcpControllerConstants.ATTRIB_REPEAT_STATUS:
+                mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT,
+                        AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+                                plAppSetting.attr_val));
+                break;
+            case AvrcpControllerConstants.ATTRIB_SCAN_STATUS:
+                mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN,
+                        AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+                                plAppSetting.attr_val));
+                break;
+            case AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS:
+                mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE,
+                        AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+                                plAppSetting.attr_val));
+                break;
+            }
+        }
+        return mAvrcpPlayerAppSetting;
+    }
+    public byte getCurrentPlayerAppSettingValue(byte mPlayerAppAttrId) {
+        for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+            if(mPlayerAppAttrId == plAppSetting.attr_Id)
+                return plAppSetting.attr_val;
+        }
+        return 0;
+    }
+    /*
+     * Checks if current setting is supported by remote.
+     * input would be in form of flattened strucuture <id,val>
+     */
+    public boolean isPlayerAppSettingSupported(byte numAttributes, byte[] playerAppSetting) {
+        for( int i = 0; (i < 2*numAttributes);) {
+            byte id = playerAppSetting[i++];
+            byte val = playerAppSetting[i++];
+            boolean found = false;
+            for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+                if(plAppSetting.attr_Id == id) {
+                    for(int j = 0; j < plAppSetting.supported_values.length; j++) {
+                        if(val == plAppSetting.supported_values[j]) {
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if(!found)
+                return false;
+        }
+        return true;
+    }
+}
+
+/*
+ * Contains information about track
+ */
+class TrackInfo extends MediaItem {
+    String mArtistName;
+    String mTrackTitle;
+    String mAlbumTitle;
+    String mGenre;
+    long mTrackNum; // number of audio file on original recording.
+    long mTotalTracks;// total number of tracks on original recording
+    long mTrackLen;// full length of AudioFile.
+    /* In case of 1.3 we have to set itemUid explicitly to 0 */
+
+    /* reset it to default values */
+    private void resetTrackInfo() {
+        mArtistName = AvrcpControllerConstants.ARTIST_NAME_INVALID;;
+        mTrackTitle = AvrcpControllerConstants.TITLE_INVALID;;
+        mAlbumTitle = AvrcpControllerConstants.ALBUM_NAME_INVALID;
+        mGenre      = AvrcpControllerConstants.GENRE_INVALID;
+        mTrackNum   = AvrcpControllerConstants.TRACK_NUM_INVALID;
+        mTotalTracks = AvrcpControllerConstants.TOTAL_TRACK_TIME_INVALID;
+        mTrackLen = AvrcpControllerConstants.TOTAL_TRACK_TIME_INVALID;
+    }
+    public TrackInfo() {
+        resetTrackInfo();
+    }
+    public TrackInfo(int mTrackId, byte mNumAttributes, int[] mAttribIds, String[] mAttribs) {
+        mItemUid = mTrackId;
+        resetTrackInfo();
+        for (int i = 0; i < mNumAttributes; i++) {
+            switch(mAttribIds[i]) {
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TITLE:
+                mTrackTitle = mAttribs[i];
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_ARTIST_NAME:
+                mArtistName = mAttribs[i];
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_ALBUM_NAME:
+                mAlbumTitle = mAttribs[i];
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TRACK_NUMBER:
+                if(!mAttribs[i].isEmpty())
+                    mTrackNum = Long.valueOf(mAttribs[i]);
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+                if(!mAttribs[i].isEmpty())
+                    mTotalTracks = Long.valueOf(mAttribs[i]);
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_GENRE:
+                mGenre = mAttribs[i];
+                break;
+            case AvrcpControllerConstants.MEDIA_ATTRIBUTE_PLAYING_TIME:
+                if(!mAttribs[i].isEmpty())
+                    mTrackLen = Long.valueOf(mAttribs[i]);
+                break;
+            }
+        }
+    }
+    public String toString() {
+        return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle +
+                " albumTitle= " + mAlbumTitle + " genre= " +mGenre+" trackNum= "+
+                Long.toString(mTrackNum) + " track_len : "+ Long.toString(mTrackLen) +
+                " TotalTracks " + Long.toString(mTotalTracks) + "]";
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java b/src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java
new file mode 100644 (file)
index 0000000..45dfd0a
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides helper classes used by other AvrcpControllerClasses.
+ * Don't change this file without changing HAL Constants in bt_rc.h
+ */
+
+final class AvrcpControllerConstants {
+
+    /*
+     * Debug flags
+     */
+    public static final boolean DBG = true;
+    public static final boolean VDBG = true;
+    /*
+     * Scopes of operation
+     */
+    public static final int AVRCP_SCOPE_NOW_PLAYING = 0;
+    public static final int AVRCP_SCOPE_VFS = 1;
+    /*
+     * Remote features
+     */
+    public static final byte BTRC_FEAT_NONE = 0;
+    public static final byte BTRC_FEAT_METADATA = 1;
+    public static final byte BTRC_FEAT_ABSOLUTE_VOLUME = 2;
+    public static final byte BTRC_FEAT_BROWSE = 4;
+
+    /*
+     *Element Id Values for GetMetaData
+    */
+    public static final int MEDIA_ATTRIBUTE_TITLE = 0x01;
+    public static final int MEDIA_ATTRIBUTE_ARTIST_NAME = 0x02;
+    public static final int MEDIA_ATTRIBUTE_ALBUM_NAME = 0x03;
+    public static final int MEDIA_ATTRIBUTE_TRACK_NUMBER = 0x04;
+    public static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
+    public static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
+    public static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+
+    /*
+     * Default values for each of the items
+    */
+    public static final int TRACK_NUM_INVALID = 0xFF;
+    public static final int STATUS_INVALID = 0xFF;
+    public static final String TITLE_INVALID = "NOT_SUPPORTED";
+    public static final String ARTIST_NAME_INVALID = "NOT_SUPPORTED";
+    public static final String ALBUM_NAME_INVALID = "NOT_SUPPORTED";
+    public static final int TOTAL_TRACKS_INVALID = 0xFFFFFFFF;
+    public static final String GENRE_INVALID = "NOT_SUPPORTED";
+    public static final int PLAYING_TIME_INVALID = 0xFF;
+    public static final int TOTAL_TRACK_TIME_INVALID = 0xFF;
+    public static final String REPEAT_STATUS_INVALID = "NOT_SUPPORTED";
+    public static final String SHUFFLE_STATUS_INVALID = "NOT_SUPPORTED";
+    public static final String SCAN_STATUS_INVALID = "NOT_SUPPORTED";
+    public static final String EQUALIZER_STATUS_INVALID = "NOT_SUPPORTED";
+    public static final String BATTERY_STATUS_INVALID = "NOT_SUPPORTED";
+    public static final String SYSTEM_STATUS_INVALID = "NOT_SUPPORTED";
+
+    /*
+     * Values for SetPlayerApplicationSettings
+    */
+    public static final byte ATTRIB_EQUALIZER_STATUS = 0x01;
+    public static final byte ATTRIB_REPEAT_STATUS = 0x02;
+    public static final byte ATTRIB_SHUFFLE_STATUS = 0x03;
+    public static final byte ATTRIB_SCAN_STATUS = 0x04;
+
+    public static final byte EQUALIZER_STATUS_OFF = 0x01;
+    public static final byte EQUALIZER_STATUS_ON = 0x02;
+
+    public static final byte REPEAT_STATUS_OFF = 0x01;
+    public static final byte REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+    public static final byte REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+    public static final byte REPEAT_STATUS_GROUP_REPEAT = 0x04;
+
+    public static final byte SHUFFLE_STATUS_OFF = 0x01;
+    public static final byte SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+    public static final byte SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+
+    public static final byte SCAN_STATUS_OFF = 0x01;
+    public static final byte SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
+    public static final byte SCAN_STATUS_GROUP_SCAN = 0x03;
+
+    /*
+     *  Play State Values
+     */
+    public static final int PLAY_STATUS_STOPPED = 0x00;
+    public static final int PLAY_STATUS_PLAYING = 0x01;
+    public static final int PLAY_STATUS_PAUSED  = 0x02;
+    public static final int PLAY_STATUS_FWD_SEEK = 0x03;
+    public static final int PLAY_STATUS_REV_SEEK = 0x04;
+    public static final int PLAY_STATUS_ERROR    = 0xFF;
+    /*
+     * System Status
+     */
+    public static final int SYSTEM_POWER_ON = 0x00;
+    public static final int SYSTEM_POWER_OFF = 0x01;
+    public static final int SYSTEM_UNPLUGGED = 0x02;
+    public static final int SYSTEM_STATUS_UNDEFINED = 0xFF;
+    /*
+     * Battery Status
+     */
+    public static final int BATT_POWER_NORMAL = 0x00;
+    public static final int BATT_POWER_WARNING = 0x01;
+    public static final int BATT_POWER_CRITICAL = 0x02;
+    public static final int BATT_POWER_EXTERNAL = 0x03;
+    public static final int BATT_POWER_FULL_CHARGE = 0x04;
+    public static final int BATT_POWER_UNDEFINED = 0xFF;
+
+    public static final int NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
+    public static final int NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
+    /*
+     * Base value for absolute volume
+     */
+    static final int ABS_VOL_BASE = 127;
+
+    public static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+    public static final int MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS = 2;
+    public static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
+
+    public static final int MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING = 101;
+    public static final int MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED = 102;
+    public static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
+    public static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
+    public static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
+    public static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
+    public static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
+
+    public static final int MESSAGE_PROCESS_RC_FEATURES = 1100;
+    public static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 1200;
+
+    public static String dumpMessageString(int message)
+    {
+        String str = "UNKNOWN";
+        switch(message)
+        {
+            case MESSAGE_SEND_PASS_THROUGH_CMD:
+                str = "REQ_PASS_THROUGH_CMD";
+                break;
+            case MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+                str = "REQ_SET_PLAYER_APP_SETTING";
+                break;
+            case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+                str = "REQ_GRP_NAV_CMD";
+                break;
+            case MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
+                str = "CB_SUPPORTED_PLAYER_APP_SETTING";
+                break;
+            case MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
+                str = "CB_PLAYER_APP_SETTING_CHANGED";
+                break;
+            case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+                str = "CB_SET_ABS_VOL_CMD";
+                break;
+            case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+                str = "CB_REGISTER_ABS_VOL";
+                break;
+            case MESSAGE_PROCESS_TRACK_CHANGED:
+                str = "CB_TRACK_CHANGED";
+                break;
+            case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                str = "CB_PLAY_POS_CHANGED";
+                break;
+            case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+                str = "CB_PLAY_STATUS_CHANGED";
+                break;
+            case MESSAGE_PROCESS_RC_FEATURES:
+                str = "CB_RC_FEATURES";
+                break;
+            case MESSAGE_PROCESS_CONNECTION_CHANGE:
+                str = "CB_CONN_CHANGED";
+                break;
+            default:
+                str = Integer.toString(message);
+                break;
+        }
+        return str;
+    }
+
+    /* Absolute Volume Notification State */
+    /* if we are in this state, we would send vol update to remote */
+    public static final int SEND_VOLUME_CHANGE_RSP = 0;
+    /* if we are in this state, we would not send vol update to remote */
+    public static final int DEFER_VOLUME_CHANGE_RSP = 1;
+    public static final int VOLUME_LABEL_UNDEFINED = 0xFF;
+}
index ed426ec..d18cfc2 100644 (file)
@@ -18,35 +18,51 @@ package com.android.bluetooth.avrcp;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpController;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
-
+import android.media.AudioManager;
+import com.android.bluetooth.a2dp.A2dpSinkService;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
-
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.HashMap;
-
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
 /**
  * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
  * @hide
  */
 public class AvrcpControllerService extends ProfileService {
-    private static final boolean DBG = false;
+    private static final boolean DBG = AvrcpControllerConstants.DBG;
+    private static final boolean VDBG = AvrcpControllerConstants.VDBG;
     private static final String TAG = "AvrcpControllerService";
 
-    private static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+/*
+ *  Messages handled by mHandler
+ */
+
+    RemoteDevice mAvrcpRemoteDevice;
+    RemoteMediaPlayers mRemoteMediaPlayers;
+    NowPlaying mRemoteNowPlayingList;
 
     private AvrcpMessageHandler mHandler;
     private static AvrcpControllerService sAvrcpControllerService;
+    private static AudioManager mAudioManager;
 
     private final ArrayList<BluetoothDevice> mConnectedDevices
             = new ArrayList<BluetoothDevice>();
@@ -74,10 +90,40 @@ public class AvrcpControllerService extends ProfileService {
         mHandler = new AvrcpMessageHandler(looper);
 
         setAvrcpControllerService(this);
+        mAudioManager = (AudioManager)sAvrcpControllerService.
+                                  getSystemService(Context.AUDIO_SERVICE);
         return true;
     }
 
+    protected void resetRemoteData() {
+        try {
+            unregisterReceiver(mBroadcastReceiver);
+        }
+        catch (IllegalArgumentException e) {
+            Log.e(TAG,"Receiver not registered");
+        }
+        if(mAvrcpRemoteDevice != null) {
+            mAvrcpRemoteDevice.cleanup();
+            mAvrcpRemoteDevice = null;
+        }
+        if(mRemoteMediaPlayers != null) {
+            mRemoteMediaPlayers.cleanup();
+            mRemoteMediaPlayers = null;
+        }
+        if(mRemoteNowPlayingList != null) {
+            mRemoteNowPlayingList.cleanup();
+            mRemoteNowPlayingList = null;
+        }
+    }
     protected boolean stop() {
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+            Looper looper = mHandler.getLooper();
+            if (looper != null) {
+                looper.quit();
+            }
+        }
+        resetRemoteData();
         return true;
     }
 
@@ -87,7 +133,7 @@ public class AvrcpControllerService extends ProfileService {
             Looper looper = mHandler.getLooper();
             if (looper != null) looper.quit();
         }
-
+        resetRemoteData();
         clearAvrcpControllerService();
         cleanupNative();
 
@@ -152,18 +198,177 @@ public class AvrcpControllerService extends ProfileService {
                                                 : BluetoothProfile.STATE_DISCONNECTED);
     }
 
-    public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
-        if (DBG) Log.d(TAG, "sendPassThroughCmd");
-        Log.v(TAG, "keyCode: " + keyCode + " keyState: " + keyState);
+    public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
         if (device == null) {
             throw new NullPointerException("device == null");
         }
+        if (!(mConnectedDevices.contains(device))) {
+            for (BluetoothDevice cdevice : mConnectedDevices) {
+                Log.e(TAG, "Device: " + cdevice);
+            }
+            Log.e(TAG," Device does not match " + device);
+            return;
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(MESSAGE_SEND_PASS_THROUGH_CMD,
-                keyCode, keyState, device);
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_SEND_GROUP_NAVIGATION_CMD,keyCode, keyState, device);
         mHandler.sendMessage(msg);
     }
 
+    public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+        if (device == null) {
+            throw new NullPointerException("device == null");
+        }
+        if (!(mConnectedDevices.contains(device))) {
+            Log.d(TAG," Device does not match");
+            return;
+        }
+        if ((mAvrcpRemoteDevice == null)||
+            (mAvrcpRemoteDevice.mRemoteFeatures == AvrcpControllerConstants.BTRC_FEAT_NONE)||
+            (mRemoteMediaPlayers == null) ||
+            (mRemoteMediaPlayers.getAddressedPlayer() == null)){
+            Log.d(TAG," Device connected but PlayState not present ");
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
+                    keyCode, keyState, device);
+            mHandler.sendMessage(msg);
+            return;
+        }
+        boolean sendCommand = false;
+        switch(keyCode) {
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY:
+                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED) ||
+                                (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING);
+                break;
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE:
+            /*
+             * allowing pause command in pause state to handle A2DP Sink Concurrency
+             * If call is ongoing and Start is initiated from remote, we will send pause again
+             * If acquireFocus fails, we will send Pause again
+             * To Stop sending multiple Pause, check in application.
+             */
+                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_REV_SEEK);
+                break;
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP:
+                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_REV_SEEK)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+                               (mRemoteMediaPlayers.getPlayStatus() ==
+                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED);
+                break;
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD:
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD:
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_FF:
+            case BluetoothAvrcpController.PASS_THRU_CMD_ID_REWIND:
+                sendCommand = true; // we can send this command in all states
+                break;
+        }
+        if (sendCommand) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
+                keyCode, keyState, device);
+            mHandler.sendMessage(msg);
+        }
+        else {
+            Log.e(TAG," Not in right state, don't send Pass Thru cmd ");
+        }
+    }
+
+    public MediaMetadata getMetaData(BluetoothDevice device) {
+        Log.d(TAG, "getMetaData = ");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if((mRemoteNowPlayingList != null) && (mRemoteNowPlayingList.getCurrentTrack() != null)) {
+            return getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0);
+        }
+        else
+            return null;
+    }
+    public PlaybackState getPlaybackState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getPlayBackState device = "+ device);
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getCurrentPlayBackState();
+    }
+    public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getPlayerApplicationSetting ");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return getCurrentPlayerAppSetting();
+    }
+    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+        if ((mAvrcpRemoteDevice == null)||(mRemoteMediaPlayers == null)) {
+            return false;
+        }
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        /*
+         * We have to extract values from BluetoothAvrcpPlayerSettings
+         */
+        int mSettings = plAppSetting.getSettings();
+        int numAttributes = 0;
+        /* calculate number of attributes in request */
+        while(mSettings > 0) {
+            numAttributes += ((mSettings & 0x01)!= 0)?1: 0;
+            mSettings = mSettings >> 1;
+        }
+        byte[] attribArray = new byte [2*numAttributes];
+        mSettings = plAppSetting.getSettings();
+        /*
+         * Now we will flatten it <id, val>
+         */
+        int i = 0;
+        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+            attribArray[i++] = AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS;
+            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+                    BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER, plAppSetting.
+                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER));
+        }
+        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+            attribArray[i++] = AvrcpControllerConstants.ATTRIB_REPEAT_STATUS;
+            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+                    BluetoothAvrcpPlayerSettings.SETTING_REPEAT, plAppSetting.
+                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT));
+        }
+        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+            attribArray[i++] = AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS;
+            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+                    BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE, plAppSetting.
+                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE));
+        }
+        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+            attribArray[i++] = AvrcpControllerConstants.ATTRIB_SCAN_STATUS;
+            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+                    BluetoothAvrcpPlayerSettings.SETTING_SCAN, plAppSetting.
+                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN));
+        }
+        boolean isSettingSupported = mRemoteMediaPlayers.getAddressedPlayer().
+                                   isPlayerAppSettingSupported((byte)numAttributes, attribArray);
+        if(isSettingSupported) {
+            ByteBuffer bb = ByteBuffer.wrap(attribArray, 0, (2*numAttributes));
+             Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS, numAttributes, 0, bb);
+            mHandler.sendMessage(msg);
+        }
+        return isSettingSupported;
+    }
+
     //Binder object: Must be static class or memory leak may occur
     private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
         implements IProfileServiceBinder {
@@ -214,8 +419,107 @@ public class AvrcpControllerService extends ProfileService {
             if (service == null) return;
             service.sendPassThroughCmd(device, keyCode, keyState);
         }
+
+        @Override
+        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+            Log.v(TAG,"Binder Call: sendGroupNavigationCmd");
+            AvrcpControllerService service = getService();
+            if (service == null) return;
+            service.sendGroupNavigationCmd(device, keyCode, keyState);
+        }
+
+        @Override
+        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+            Log.v(TAG,"Binder Call: getPlayerApplicationSetting ");
+            AvrcpControllerService service = getService();
+            if (service == null) return null;
+            return service.getPlayerSettings(device);
+        }
+
+        @Override
+        public MediaMetadata getMetadata(BluetoothDevice device) {
+            Log.v(TAG,"Binder Call: getMetaData ");
+            AvrcpControllerService service = getService();
+            if (service == null) return null;
+            return service.getMetaData(device);
+        }
+
+        @Override
+        public PlaybackState getPlaybackState(BluetoothDevice device) {
+            Log.v(TAG,"Binder Call: getPlaybackState");
+            AvrcpControllerService service = getService();
+            if (service == null) return null;
+            return service.getPlaybackState(device);
+        }
+
+        @Override
+        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+            Log.v(TAG,"Binder Call: setPlayerApplicationSetting " );
+            AvrcpControllerService service = getService();
+            if (service == null) return false;
+            return service.setPlayerApplicationSetting(plAppSetting);
+        }
     };
 
+    private String utf8ToString(byte[] input)
+    {
+        Charset UTF8_CHARSET = Charset.forName("UTF-8");
+        return new String(input,UTF8_CHARSET);
+    }
+    private int asciiToInt(int len, byte[] array)
+    {
+        return Integer.parseInt(utf8ToString(array));
+    }
+    private BluetoothAvrcpPlayerSettings getCurrentPlayerAppSetting() {
+        if((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null))
+            return null;
+        return mRemoteMediaPlayers.getAddressedPlayer().getSupportedPlayerAppSetting();
+    }
+    private PlaybackState getCurrentPlayBackState() {
+        if ((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) {
+            return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
+                                                        PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+                                                        0).build();
+        }
+        return AvrcpUtils.mapBtPlayStatustoPlayBackState(
+                mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+                mRemoteMediaPlayers.getAddressedPlayer().mPlayTime);
+    }
+    private MediaMetadata getCurrentMetaData(int scope, int trackId) {
+        /* if scope is now playing */
+        if(scope == AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING) {
+            if((mRemoteNowPlayingList == null) || (mRemoteNowPlayingList.
+                                                           getTrackFromId(trackId) == null))
+                return null;
+            TrackInfo mNowPlayingTrack = mRemoteNowPlayingList.getTrackFromId(trackId);
+            return AvrcpUtils.getMediaMetaData(mNowPlayingTrack);
+        }
+        /* if scope is now playing */
+        else if(scope == AvrcpControllerConstants.AVRCP_SCOPE_VFS) {
+            /* TODO for browsing */
+        }
+        return null;
+    }
+    private void broadcastMetaDataChanged(MediaMetadata mMetaData) {
+        Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+        intent.putExtra(BluetoothAvrcpController.EXTRA_METADATA, mMetaData);
+        if(DBG) Log.d(TAG," broadcastMetaDataChanged = " +
+                                                   AvrcpUtils.displayMetaData(mMetaData));
+        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+    private void broadcastPlayBackStateChanged(PlaybackState mState) {
+        Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+        intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYBACK, mState);
+        if(DBG) Log.d(TAG," broadcastPlayBackStateChanged = " + mState.toString());
+        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+    private void broadcastPlayerAppSettingChanged(BluetoothAvrcpPlayerSettings mPlAppSetting) {
+        Intent intent = new Intent(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
+        intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYER_SETTING, mPlAppSetting);
+        if(DBG) Log.d(TAG," broadcastPlayerAppSettingChanged = " +
+                AvrcpUtils.displayBluetoothAvrcpSettings(mPlAppSetting));
+        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
     /** Handles Avrcp messages. */
     private final class AvrcpMessageHandler extends Handler {
         private AvrcpMessageHandler(Looper looper) {
@@ -224,39 +528,365 @@ public class AvrcpControllerService extends ProfileService {
 
         @Override
         public void handleMessage(Message msg) {
+            Log.d(TAG," HandleMessage: "+ AvrcpControllerConstants.dumpMessageString(msg.what) +
+                  " Remote Connected " + !mConnectedDevices.isEmpty());
             switch (msg.what) {
-            case MESSAGE_SEND_PASS_THROUGH_CMD:
-                if (DBG) Log.v(TAG, "MESSAGE_SEND_PASS_THROUGH_CMD");
+            case AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD:
                 BluetoothDevice device = (BluetoothDevice)msg.obj;
                 sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
+                A2dpSinkService a2dpSnkService = A2dpSinkService.getA2dpSinkService();
+                if((a2dpSnkService != null)&&(!mConnectedDevices.isEmpty())) {
+                    Log.d(TAG," inform AVRCP Commands to A2DP Sink ");
+                    a2dpSnkService.informAvrcpPassThroughCmd(device,
+                            msg.arg1, msg.arg2);
+                }
+                break;
+            case AvrcpControllerConstants.MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+                BluetoothDevice peerDevice = (BluetoothDevice)msg.obj;
+                sendGroupNavigationCommandNative(getByteAddress(peerDevice), msg.arg1, msg.arg2);
+                break;
+            case AvrcpControllerConstants.MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+                byte numAttributes = (byte)msg.arg1;
+                ByteBuffer bbRsp = (ByteBuffer)msg.obj;
+                byte[] attributeIds = new byte [numAttributes];
+                byte[] attributeVals = new byte [numAttributes];
+                for(int i = 0; (bbRsp.hasRemaining())&&(i < numAttributes); i++) {
+                    attributeIds[i] = bbRsp.get();
+                    attributeVals[i] = bbRsp.get();
+                }
+                setPlayerApplicationSettingValuesNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+                        numAttributes, attributeIds, attributeVals);
+                break;
+
+            case AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE:
+                int newState = msg.arg1;
+                int oldState = msg.arg2;
+                BluetoothDevice rtDevice =  (BluetoothDevice)msg.obj;
+                if ((newState == BluetoothProfile.STATE_CONNECTED) &&
+                    (oldState == BluetoothProfile.STATE_DISCONNECTED)) {
+                    /* We create RemoteDevice and MediaPlayerList here
+                     * Now playing list after RC features
+                     */
+                    if(mAvrcpRemoteDevice == null){
+                        mAvrcpRemoteDevice =  new RemoteDevice(rtDevice);
+                        /* Remote will have a player irrespective of AVRCP Version
+                         * Create a Default player, we will add entries in case Browsing
+                         * is supported by remote
+                         */
+                        if(mRemoteMediaPlayers == null) {
+                            mRemoteMediaPlayers = new RemoteMediaPlayers(mAvrcpRemoteDevice);
+                            PlayerInfo mPlayer = new PlayerInfo();
+                            mPlayer.mPlayerId = 0;
+                            mRemoteMediaPlayers.addPlayer(mPlayer);
+                            mRemoteMediaPlayers.setAddressedPlayer(mPlayer);
+                        }
+                    }
+                }
+                else if ((newState == BluetoothProfile.STATE_DISCONNECTED) &&
+                        (oldState == BluetoothProfile.STATE_CONNECTED)) /* connection down */
+                {
+                    resetRemoteData();
+                    mHandler.removeCallbacksAndMessages(null);
+                }
+                /*
+                 * Send intent now
+                 */
+                Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+                intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
+                intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+//              intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES:
+                if(mAvrcpRemoteDevice == null)
+                    break;
+                mAvrcpRemoteDevice.mRemoteFeatures = msg.arg1;
+                /* in case of AVRCP version < 1.3, no need to add track info */
+                if(mAvrcpRemoteDevice.isMetaDataSupported()) {
+                    if(mRemoteNowPlayingList == null)
+                        mRemoteNowPlayingList = new NowPlaying(mAvrcpRemoteDevice);
+                    TrackInfo mTrack = new TrackInfo();
+                    /* First element of NowPlayingList will be current Track
+                     * for 1.3 this will be the only song
+                     * for >= 1.4, others songs will have non-zero UID
+                     */
+                    mTrack.mItemUid = 0;
+                    mRemoteNowPlayingList.addTrack(mTrack);
+                    mRemoteNowPlayingList.setCurrTrack(mTrack);
+                }
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+                mAvrcpRemoteDevice.mAbsVolNotificationState =
+                                         AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+                setAbsVolume(msg.arg1, msg.arg2);
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+                /* start BroadcastReceiver now */
+                IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+                mAvrcpRemoteDevice.mNotificationLabel = msg.arg1;
+                mAvrcpRemoteDevice.mAbsVolNotificationState =
+                        AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
+                registerReceiver(mBroadcastReceiver, filter);
+                int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+                int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+                int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume);
+                Log.d(TAG," Sending Interim Response = "+ percentageVol + " label " + msg.arg1);
+                sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+                        (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_INTERIM, percentageVol,
+                        mAvrcpRemoteDevice.mNotificationLabel);
                 break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_TRACK_CHANGED:
+                if(mRemoteNowPlayingList != null) {
+                    mRemoteNowPlayingList.updateCurrentTrack((TrackInfo)msg.obj);
+                    broadcastMetaDataChanged(AvrcpUtils.getMediaMetaData
+                                       (mRemoteNowPlayingList.getCurrentTrack()));
+                }
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                if(mRemoteMediaPlayers != null) {
+                    mRemoteMediaPlayers.getAddressedPlayer().mPlayTime = msg.arg2;
+                    broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
+                            (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+                                    mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
+                }
+                if(mRemoteNowPlayingList != null) {
+                    mRemoteNowPlayingList.getCurrentTrack().mTrackLen = msg.arg1;
+                }
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+                if(mRemoteMediaPlayers != null) {
+                    mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus = (byte)msg.arg1;
+                    broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
+                            (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+                                    mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
+                    if(mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.
+                                                                        PLAY_STATUS_PLAYING) {
+                        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+                        if((a2dpSinkService != null)&&(!mConnectedDevices.isEmpty())) {
+                            Log.d(TAG," State = PLAYING, inform A2DP SINK");
+                            a2dpSinkService.informAvrcpStatePlaying(mConnectedDevices.get(0));
+                        }
+                    }
+                }
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
+                if(mRemoteMediaPlayers != null)
+                    mRemoteMediaPlayers.getAddressedPlayer().
+                                           setSupportedPlayerAppSetting((ByteBuffer)msg.obj);
+                break;
+            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
+                if(mRemoteMediaPlayers != null) {
+                    mRemoteMediaPlayers.getAddressedPlayer().
+                                           updatePlayerAppSetting((ByteBuffer)msg.obj);
+                    broadcastPlayerAppSettingChanged(getCurrentPlayerAppSetting());
+                }
+                break;
+            }
+        }
+    }
+
+    private void setAbsVolume(int absVol, int label)
+    {
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        if (mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd) {
+            int newIndex = (maxVolume*absVol)/AvrcpControllerConstants.ABS_VOL_BASE;
+            Log.d(TAG," setAbsVolume ="+absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
+                                              " new = "+newIndex);
+            /*
+             * In some cases change in percentage is not sufficient enough to warrant
+             * change in index values which are in range of 0-15. For such cases
+             * no action is required
+             */
+            if (newIndex != currIndex) {
+                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+                                                     AudioManager.FLAG_SHOW_UI);
+            }
+        }
+        else {
+            mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd = true;
+            absVol = (currIndex*AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume;
+            Log.d(TAG," SetAbsVol recvd for first time, respond with " + absVol);
+        }
+        sendAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), absVol, label);
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                if (streamType == AudioManager.STREAM_MUSIC) {
+                    int streamValue = intent
+                            .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+                    int streamPrevValue = intent.getIntExtra(
+                            AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+                    if (streamValue != -1 && streamValue != streamPrevValue) {
+                        if ((mAvrcpRemoteDevice == null)
+                            ||((mAvrcpRemoteDevice.mRemoteFeatures &
+                                    AvrcpControllerConstants.BTRC_FEAT_ABSOLUTE_VOLUME) == 0)
+                            ||(mConnectedDevices.isEmpty()))
+                            return;
+                        if(mAvrcpRemoteDevice.mAbsVolNotificationState ==
+                                AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP) {
+                            int maxVol = mAudioManager.
+                                                  getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+                            int currIndex = mAudioManager.
+                                                  getStreamVolume(AudioManager.STREAM_MUSIC);
+                            int percentageVol = ((currIndex*
+                                            AvrcpControllerConstants.ABS_VOL_BASE)/maxVol);
+                            Log.d(TAG," Abs Vol Notify Rsp Changed vol = "+ percentageVol);
+                            sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+                                (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_CHANGED,
+                                    percentageVol, mAvrcpRemoteDevice.mNotificationLabel);
+                        }
+                        else if (mAvrcpRemoteDevice.mAbsVolNotificationState ==
+                                AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP) {
+                            Log.d(TAG," Don't Complete Notification Rsp. ");
+                            mAvrcpRemoteDevice.mAbsVolNotificationState =
+                                              AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
+                        }
+                    }
+                }
             }
         }
+    };
+
+    private void handlePassthroughRsp(int id, int keyState) {
+        Log.d(TAG, "passthrough response received as: key: "
+                                + id + " state: " + keyState);
     }
 
     private void onConnectionStateChanged(boolean connected, byte[] address) {
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
             (Utils.getAddressStringFromByte(address));
-        Log.d(TAG, "onConnectionStateChanged " + connected + " " + device);
-        Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+        Log.d(TAG, "onConnectionStateChanged " + connected + " " + device+ " size "+
+                    mConnectedDevices.size());
+        if (device == null)
+            return;
         int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
                                                         : BluetoothProfile.STATE_DISCONNECTED);
         int newState = (connected ? BluetoothProfile.STATE_CONNECTED
                                   : BluetoothProfile.STATE_DISCONNECTED);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-//        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
         if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+            /* AVRCPControllerService supports single connection */
+            if(mConnectedDevices.size() > 0) {
+                Log.d(TAG,"A Connection already exists, returning");
+                return;
+            }
             mConnectedDevices.add(device);
+            Message msg =  mHandler.obtainMessage(
+                    AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+                        oldState, device);
+            mHandler.sendMessage(msg);
         } else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
             mConnectedDevices.remove(device);
+            Message msg =  mHandler.obtainMessage(
+                    AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+                        oldState, device);
+            mHandler.sendMessage(msg);
         }
     }
 
-    private void handlePassthroughRsp(int id, int keyState) {
-        Log.d(TAG, "passthrough response received as: key: "
+    private void getRcFeatures(byte[] address, int features) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        Message msg = mHandler.obtainMessage(
+                AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
+        mHandler.sendMessage(msg);
+    }
+    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
+              /* TODO do we need to do anything here */
+    }
+    private void handleRegisterNotificationAbsVol(byte[] address, byte label)
+    {
+        Log.d(TAG,"handleRegisterNotificationAbsVol ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, label, 0);
+        mHandler.sendMessage(msg);
+    }
+
+    private void handleSetAbsVolume(byte[] address, byte absVol, byte label)
+    {
+        Log.d(TAG,"handleSetAbsVolume ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        Message msg = mHandler.obtainMessage(
+                AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
+        mHandler.sendMessage(msg);
+    }
+
+    private void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
+                                               String[] attribVals)
+    {
+        Log.d(TAG,"onTrackChanged ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        TrackInfo mTrack = new TrackInfo(0, numAttributes, attributes, attribVals);
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_TRACK_CHANGED, numAttributes, 0, mTrack);
+        mHandler.sendMessage(msg);
+    }
+
+    private void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
+        Log.d(TAG,"onPlayPositionChanged ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
+        mHandler.sendMessage(msg);
+    }
+
+    private void onPlayStatusChanged(byte[] address, byte playStatus) {
+        if(DBG) Log.d(TAG,"onPlayStatusChanged " + playStatus);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playStatus, 0);
+        mHandler.sendMessage(msg);
+    }
+
+    private void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
+        Log.d(TAG,"handlePlayerAppSetting rspLen = " + rspLen);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING, 0, 0, bb);
+        mHandler.sendMessage(msg);
+    }
+
+    private void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
+        Log.d(TAG,"onPlayerAppSettingChanged ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+                (Utils.getAddressStringFromByte(address));
+        if (!mConnectedDevices.contains(device))
+            return;
+        ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
+        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+                MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED, 0, 0, bb);
+        mHandler.sendMessage(msg);
+    }
+
+    private void handleGroupNavigationRsp(int id, int keyState) {
+        Log.d(TAG, "group navigation response received as: key: "
                                 + id + " state: " + keyState);
     }
 
@@ -273,4 +903,13 @@ public class AvrcpControllerService extends ProfileService {
     private native void initNative();
     private native void cleanupNative();
     private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+    private native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+                                                                                     int keyState);
+    private native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+                                                    byte[] atttibIds, byte[]attribVal);
+    /* This api is used to send response to SET_ABS_VOL_CMD */
+    private native void sendAbsVolRspNative(byte[] address, int absVol, int label);
+    /* This api is used to inform remote for any volume level changes */
+    private native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+                                                    int label);
 }
diff --git a/src/com/android/bluetooth/avrcp/NowPlaying.java b/src/com/android/bluetooth/avrcp/NowPlaying.java
new file mode 100644 (file)
index 0000000..f78d7a6
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ * TODO(sanketa): Rip out this feature as this is part of 1.6.
+ * @hide
+ */
+public class NowPlaying {
+    private static final boolean DBG = true;
+    private static final String TAG = "NowPlaying";
+
+    RemoteDevice mDevice;
+    private TrackInfo mCurrTrack;
+
+    private ArrayList<TrackInfo> mNowPlayingList;
+
+    public NowPlaying(RemoteDevice mRemoteDevice) {
+        mDevice = mRemoteDevice;
+        mNowPlayingList = new ArrayList<TrackInfo>();
+        mCurrTrack = null;
+    }
+
+    public void cleanup() {
+        mDevice = null;
+        if(mNowPlayingList != null) {
+            mNowPlayingList.clear();
+        }
+        mCurrTrack = null;
+    }
+
+    public RemoteDevice getDeviceRecords() {
+        return mDevice;
+    }
+
+    public void addTrack (TrackInfo mTrack) {
+        if(mNowPlayingList != null) {
+            mNowPlayingList.add(mTrack);
+        }
+    }
+
+    public void setCurrTrack (TrackInfo mTrack) {
+        mCurrTrack = mTrack;
+    }
+
+    public TrackInfo getCurrentTrack() {
+        return mCurrTrack;
+    }
+
+    public void updateCurrentTrack(TrackInfo mTrack) {
+        mCurrTrack.mAlbumTitle = mTrack.mAlbumTitle;
+        mCurrTrack.mArtistName = mTrack.mArtistName;
+        mCurrTrack.mGenre = mTrack.mGenre;
+        mCurrTrack.mTotalTracks = mTrack.mTotalTracks;
+        mCurrTrack.mTrackLen = mTrack.mTrackLen;
+        mCurrTrack.mTrackTitle = mTrack.mTrackTitle;
+        mCurrTrack.mTrackNum = mTrack.mTrackNum;
+    }
+
+    public TrackInfo getTrackFromId(int mTrackId) {
+        if(mTrackId == 0)
+            return getCurrentTrack();
+        else {
+            for(TrackInfo mTrackInfo: mNowPlayingList) {
+                if(mTrackInfo.mItemUid == mTrackId)
+                    return mTrackInfo;
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java b/src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java
new file mode 100644 (file)
index 0000000..177742a
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ * TODO(sanketa): Rip out this feature as this is part of 1.6.
+ * @hide
+ */
+public class RemoteMediaPlayers {
+    private static final boolean DBG = true;
+    private static final String TAG = "RemoteMediaPlayers";
+
+    RemoteDevice mDevice;
+    private PlayerInfo mAddressedPlayer;
+    private PlayerInfo mBrowsedPlayer;
+    ArrayList<PlayerInfo> mMediaPlayerList;
+
+    public RemoteMediaPlayers (RemoteDevice mRemoteDevice) {
+        mDevice = mRemoteDevice;
+        mAddressedPlayer = null;
+        mBrowsedPlayer = null;
+        mMediaPlayerList = new ArrayList<PlayerInfo>();
+    }
+
+    public void cleanup() {
+        mDevice = null;
+        mAddressedPlayer = null;
+        mBrowsedPlayer = null;
+        if(mMediaPlayerList != null)
+            mMediaPlayerList.clear();
+    }
+    /*
+     * add a Player
+     */
+    public void addPlayer (PlayerInfo mPlayer) {
+        if(mMediaPlayerList != null)
+            mMediaPlayerList.add(mPlayer);
+    }
+    /*
+     * add players and Set AddressedPlayer and BrowsePlayer
+     */
+    public void setAddressedPlayer(PlayerInfo mPlayer) {
+        mAddressedPlayer = mPlayer;
+    }
+
+    public void setBrowsedPlayer(PlayerInfo mPlayer) {
+        mBrowsedPlayer = mPlayer;
+    }
+    /*
+     * Returns the currently addressed, browsed player
+     */
+    public PlayerInfo getAddressedPlayer() {
+        return mAddressedPlayer;
+    }
+
+    /*
+     * getPlayStatus of addressed player
+     */
+    public byte getPlayStatus() {
+        if(getAddressedPlayer() != null)
+            return getAddressedPlayer().mPlayStatus;
+        else
+            return AvrcpControllerConstants.PLAY_STATUS_STOPPED;
+    }
+
+    /*
+     * getPlayStatus of addressed player
+     */
+    public long getPlayPosition() {
+        if(getAddressedPlayer() != null)
+            return getAddressedPlayer().mPlayTime;
+        else
+            return AvrcpControllerConstants.PLAYING_TIME_INVALID;
+    }
+
+}
index a12ea9e..ea06baa 100644 (file)
@@ -57,6 +57,7 @@ import android.util.Log;
 import android.util.Pair;
 
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dp.A2dpSinkService;
 import com.android.bluetooth.hid.HidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hdp.HealthService;
@@ -225,6 +226,7 @@ public class AdapterService extends Service {
     private void processInitProfilePriorities (BluetoothDevice device, ParcelUuid[] uuids){
         HidService hidService = HidService.getHidService();
         A2dpService a2dpService = A2dpService.getA2dpService();
+        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
         HeadsetService headsetService = HeadsetService.getHeadsetService();
 
         // Set profile priorities only for the profiles discovered on the remote device.
@@ -259,6 +261,9 @@ public class AdapterService extends Service {
             connectOtherProfile(device, PROFILE_CONN_CONNECTED);
             setProfileAutoConnectionPriority(device, profileId);
         }
+        if ((profileId == BluetoothProfile.A2DP_SINK) && (newState == BluetoothProfile.STATE_CONNECTED)) {
+            setProfileAutoConnectionPriority(device, profileId);
+        }
         IBluetooth.Stub binder = mBinder;
         if (binder != null) {
             try {
@@ -1480,6 +1485,7 @@ public class AdapterService extends Service {
              debugLog( "autoConnect() - Initiate auto connection on BT on...");
              autoConnectHeadset();
              autoConnectA2dp();
+             autoConnectA2dpSink();
          }
          else {
              debugLog( "autoConnect() - BT is in quiet mode. Not initiating auto connections");
@@ -1514,6 +1520,17 @@ public class AdapterService extends Service {
             }
         }
     }
+     private void autoConnectA2dpSink(){
+         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+         BluetoothDevice bondedDevices[] = getBondedDevices();
+         if ((bondedDevices == null) ||(a2dpSinkService == null)) {
+             return;
+         }
+         for (BluetoothDevice device : bondedDevices) {
+             debugLog("autoConnectA2dp() - Connecting A2DP Sink with " + device.toString());
+             a2dpSinkService.connect(device);
+         }
+     }
 
      public void connectOtherProfile(BluetoothDevice device, int firstProfileStatus){
         if ((mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES) == false) &&
@@ -1587,14 +1604,6 @@ public class AdapterService extends Service {
                  hsService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
              }
          }
-         else if (profileId ==  BluetoothProfile.A2DP) {
-             A2dpService a2dpService = A2dpService.getA2dpService();
-             if ((a2dpService != null) &&
-                (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))){
-                 adjustOtherSinkPriorities(a2dpService, device);
-                 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
-             }
-         }
     }
 
      boolean cancelBondProcess(BluetoothDevice device) {