<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"/>
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;
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) {
method_onAudioConfigChanged =
env->GetMethodID(clazz, "onAudioConfigChanged", "([BII)V");
+ method_onAudioFocusRequested =
+ env->GetMethodID(clazz, "onAudioFocusRequested", "([B)V");
+
ALOGI("%s: succeeds", __FUNCTION__);
}
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)
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;
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;
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__);
}
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)
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;
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);
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;
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;
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;
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;
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
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>();
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);
}
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) {
@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
@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
// Upon connected, the audio starts out as stopped
broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
BluetoothA2dpSink.STATE_PLAYING);
+ mAvrcpPlaySent = false;
}
@Override
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) {
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);
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);
}
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);
}
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;
// 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();
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));
}
}
}
+ 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;
private native void cleanupNative();
private native boolean connectA2dpNative(byte[] address);
private native boolean disconnectA2dpNative(byte[] address);
+ private native void informAudioFocusStateNative(int focusGranted);
}
--- /dev/null
+/*
+ * 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) + "]";
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
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>();
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;
}
Looper looper = mHandler.getLooper();
if (looper != null) looper.quit();
}
-
+ resetRemoteData();
clearAvrcpControllerService();
cleanupNative();
: 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 {
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) {
@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);
}
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);
}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
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;
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.
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 {
debugLog( "autoConnect() - Initiate auto connection on BT on...");
autoConnectHeadset();
autoConnectA2dp();
+ autoConnectA2dpSink();
}
else {
debugLog( "autoConnect() - BT is in quiet mode. Not initiating auto connections");
}
}
}
+ 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) &&
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) {