summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnubhavGupta <anubhavg@codeaurora.org>2015-02-27 19:08:36 +0530
committerLinux Build Service Account <lnxbuild@localhost>2015-10-06 03:25:43 -0600
commit442962cde4c933ba91eeeb422e48d063fa5fa0fb (patch)
treed8d1d124172a0675397899fc42f0daf211f13d0a
parentdb525f6a5d47ea8934dd58cffff452da89489f30 (diff)
downloadandroid_packages_apps_Bluetooth-442962cde4c933ba91eeeb422e48d063fa5fa0fb.tar.gz
android_packages_apps_Bluetooth-442962cde4c933ba91eeeb422e48d063fa5fa0fb.tar.bz2
android_packages_apps_Bluetooth-442962cde4c933ba91eeeb422e48d063fa5fa0fb.zip
Bluetooth : Support for AVRCP 1.3 Controller
- Content Provider Implementation for AVRCP MetaData - Handling of AVRCP Commands and Events - Interface for AudioManger to support AbsVol Change-Id: I5e1f768ddfa6c089667874c5151a412ae79052bd Bluetooth: Avrcp Controller 1.3 Support - send GetPlayStatus on state transition from paused/stopped to playing. - indentation correction Change-Id: Ifa259038383e0652ff7dcb345367d8f67c3c80be Bluetooth: AVRCP Controller fixes - Reset metadata if remote does not support a particular Element Attribute - Fetch playerapplicationattributes on track change - Proper update of negative values from remote Usecase: Switch player on TG and check metadata, playerappsetting Failure: Remote device does not call Event_APP_SETTING_CHANGED when we switch between player. If new player does not update metadata, we were showing stale data Solution: Reset metadata if not supported by remote send getplayerapplicatoin on track change Change-Id: Ia7859261336ccdb447d330626b434fefd4abee6f
-rw-r--r--[-rwxr-xr-x]AndroidManifest.xml15
-rw-r--r--jni/com_android_bluetooth_avrcp_controller.cpp558
-rw-r--r--res/values/strings.xml15
-rw-r--r--src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java4
-rw-r--r--src/com/android/bluetooth/avrcp/AvrcpControllerService.java1915
-rw-r--r--src/com/android/bluetooth/avrcp/BluetoothAvrcpDataProvider.java382
6 files changed, 2877 insertions, 12 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 44613341a..0fa1b7674 100755..100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -11,6 +11,11 @@
android:description="@string/permdesc_bluetoothShareManager"
android:protectionLevel="signature" />
+ <permission android:name="android.permission.ACCESS_BLUETOOTH_AVRCP_CT_DATA"
+ android:label="@string/permlab_bluetoothAvrcpDataManager"
+ android:description="@string/permdesc_bluetoothAvrcpDataManager"
+ android:protectionLevel="signature" />
+
<!-- Allows temporarily whitelisting Bluetooth addresses for sharing -->
<permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE"
android:label="@string/permlab_bluetoothWhitelist"
@@ -20,6 +25,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_AVRCP_CT_DATA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -83,6 +89,14 @@
android:pathPrefix="/btopp"
android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
</provider>
+ <provider android:name=".avrcp.BluetoothAvrcpDataProvider"
+ android:authorities="com.android.bluetooth.avrcp"
+ android:exported="true"
+ android:process="@string/process">
+ <path-permission
+ android:pathPrefix="/btavrcp_ct"
+ android:permission="android.permission.ACCESS_BLUETOOTH_AVRCP_CT_DATA" />
+ </provider>
<service
android:process="@string/process"
android:name = ".btservice.AdapterService">
@@ -297,6 +311,7 @@
<service
android:process="@string/process"
android:name = ".avrcp.AvrcpControllerService"
+ android:permission="android.permission.ACCESS_BLUETOOTH_AVRCP_CT_DATA"
android:enabled="@bool/profile_supported_avrcp_controller">
<intent-filter>
<action android:name="android.bluetooth.IBluetoothAvrcpController" />
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 9e32721e9..1fa588911 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -1,4 +1,6 @@
/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +30,18 @@
namespace android {
static jmethodID method_handlePassthroughRsp;
static jmethodID method_onConnectionStateChanged;
+static jmethodID method_getRcFeatures;
+static jmethodID method_handleGetCapabilitiesResponse;
+static jmethodID method_handleListPlayerApplicationSettingsAttrib;
+static jmethodID method_handleListPlayerApplicationSettingValue;
+static jmethodID method_handleCurrentPlayerApplicationSettingsResponse;
+static jmethodID method_handleNotificationRsp;
+static jmethodID method_handleGetElementAttributes;
+static jmethodID method_handleGetPlayStatus;
+static jmethodID method_handleSetAbsVolume;
+static jmethodID method_handleRegisterNotificationAbsVol;
+static jmethodID method_handleSetPlayerApplicationResponse;
+
static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
static jobject mCallbacksObj = NULL;
@@ -84,11 +98,323 @@ static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr)
sCallbackEnv->DeleteLocalRef(addr);
}
+static void btavrcp_get_rcfeatures_callback(bt_bdaddr_t *bd_addr, int features) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_getcap_rsp_callback(bt_bdaddr_t *bd_addr, int cap_id,
+ uint32_t* supported_values, int num_supported, uint8_t rsp_type) {
+ jbyteArray addr;
+ jintArray supported_val = NULL;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewIntArray(num_supported);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetIntArrayRegion(supported_val, 0, (num_supported), (jint*)(supported_values));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGetCapabilitiesResponse, addr,
+ (jint)cap_id,supported_val, (jint)num_supported, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_list_player_app_setting_attrib_rsp_callback(bt_bdaddr_t *bd_addr,
+ uint8_t* supported_attribs, int num_attrib, uint8_t rsp_type) {
+ jbyteArray addr;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(num_attrib);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0, (num_attrib), (jbyte*)(supported_attribs));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleListPlayerApplicationSettingsAttrib,
+ addr, supported_val,(jint)num_attrib, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_list_player_app_setting_value_rsp_callback(bt_bdaddr_t *bd_addr,
+ uint8_t* supported_values, uint8_t num_supported, uint8_t rsp_type) {
+ jbyteArray addr;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(num_supported);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0,(num_supported), (jbyte*)(supported_values));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleListPlayerApplicationSettingValue,
+ addr, supported_val,(jbyte)num_supported, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_current_player_app_setting_rsp_callback(bt_bdaddr_t *bd_addr,
+ uint8_t* supported_ids,uint8_t* supported_values, uint8_t num_attrib, uint8_t rsp_type) {
+ jbyteArray addr;
+ jbyteArray supported_attrib_ids;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(num_attrib);
+ supported_attrib_ids = sCallbackEnv->NewByteArray(num_attrib);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0, (num_attrib), (jbyte*)(supported_values));
+ sCallbackEnv->SetByteArrayRegion(supported_attrib_ids, 0, (num_attrib),
+ (jbyte*)(supported_ids));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj,
+ method_handleCurrentPlayerApplicationSettingsResponse,addr,supported_attrib_ids,
+ supported_val, (jbyte)num_attrib, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+ sCallbackEnv->DeleteLocalRef(supported_attrib_ids);
+}
+
+static void btavrcp_set_player_app_setting_rsp_callback(bt_bdaddr_t *bd_addr,uint8_t rsp_type) {
+ 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_handleSetPlayerApplicationResponse,
+ addr,(jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_notification_rsp_callback(bt_bdaddr_t *bd_addr, uint8_t rsp_type,
+ int rsp_len, uint8_t* notification_rsp) {
+ jbyteArray addr;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(rsp_len);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0, (rsp_len), (jbyte*)(notification_rsp));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleNotificationRsp,
+ addr, (jbyte)rsp_type, (jint)rsp_len, supported_val);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_get_element_attrib_rsp_callback(bt_bdaddr_t *bd_addr, uint8_t num_attributes,
+ int rsp_len, uint8_t* attrib_rsp,uint8_t rsp_type) {
+ jbyteArray addr;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(rsp_len);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0, (rsp_len), (jbyte*)(attrib_rsp));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGetElementAttributes,
+ addr, (jbyte)num_attributes, (jint)rsp_len,supported_val, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_get_playstatus_rsp_callback(bt_bdaddr_t *bd_addr, int param_len,
+ uint8_t* play_status_rsp,uint8_t rsp_type) {
+ jbyteArray addr;
+ jbyteArray supported_val;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ supported_val = sCallbackEnv->NewByteArray(param_len);
+ if ((!addr)||(!supported_val)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(supported_val, 0, (param_len), (jbyte*)(play_status_rsp));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGetPlayStatus,
+ addr, (jint)param_len, supported_val, (jbyte)rsp_type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(supported_val);
+}
+
+static void btavrcp_set_abs_vol_cmd_callback(bt_bdaddr_t *bd_addr, uint8_t abs_vol) {
+ 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);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_register_notification_absvol_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 get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleRegisterNotificationAbsVol, addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_passthrough_response_callback,
- btavrcp_connection_state_callback
+ btavrcp_connection_state_callback,
+ btavrcp_get_rcfeatures_callback,
+ btavrcp_getcap_rsp_callback,
+ btavrcp_list_player_app_setting_attrib_rsp_callback,
+ btavrcp_list_player_app_setting_value_rsp_callback,
+ btavrcp_current_player_app_setting_rsp_callback,
+ btavrcp_set_player_app_setting_rsp_callback,
+ btavrcp_notification_rsp_callback,
+ btavrcp_get_element_attrib_rsp_callback,
+ btavrcp_get_playstatus_rsp_callback,
+ btavrcp_set_abs_vol_cmd_callback,
+ btavrcp_register_notification_absvol_callback
};
static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -98,6 +424,39 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
+ method_getRcFeatures =
+ env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
+
+ method_handleGetCapabilitiesResponse =
+ env->GetMethodID(clazz, "handleGetCapabilitiesResponse", "([BI[IIB)V");
+
+ method_handleListPlayerApplicationSettingsAttrib =
+ env->GetMethodID(clazz, "handleListPlayerApplicationSettingsAttrib", "([B[BIB)V");
+
+ method_handleListPlayerApplicationSettingValue =
+ env->GetMethodID(clazz, "handleListPlayerApplicationSettingValue", "([B[BBB)V");
+
+ method_handleCurrentPlayerApplicationSettingsResponse =
+ env->GetMethodID(clazz, "handleCurrentPlayerApplicationSettingsResponse", "([B[B[BBB)V");
+
+ method_handleNotificationRsp =
+ env->GetMethodID(clazz, "handleNotificationRsp", "([BBI[B)V");
+
+ method_handleGetElementAttributes =
+ env->GetMethodID(clazz, "handleGetElementAttributes", "([BBI[BB)V");
+
+ method_handleGetPlayStatus =
+ env->GetMethodID(clazz, "handleGetPlayStatus", "([BI[BB)V");
+
+ method_handleSetAbsVolume =
+ env->GetMethodID(clazz, "handleSetAbsVolume", "([BB)V");
+
+ method_handleRegisterNotificationAbsVol =
+ env->GetMethodID(clazz, "handleRegisterNotificationAbsVol", "([B)V");
+
+ method_handleSetPlayerApplicationResponse =
+ env->GetMethodID(clazz, "handleSetPlayerApplicationResponse", "([BB)V");
+
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -183,12 +542,205 @@ static jboolean sendPassThroughCommandNative(JNIEnv *env, jobject object, jbyteA
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static void getCapabilitiesNative(JNIEnv *env, jobject object,jint capability_id) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->getcapabilities_cmd((uint8_t)capability_id))
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getCapabilitiesNative command, status: %d", status);
+ }
+}
+
+static void listPlayerApplicationSettingAttributeNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->list_player_app_setting_attrib_cmd())
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending listPlAppSettAttribNative command,status: %d", status);
+ }
+}
+
+static void listPlayerApplicationSettingValueNative(JNIEnv *env, jobject object, jbyte attrib_id) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->list_player_app_setting_value_cmd((uint8_t)attrib_id))
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending listPlAppSettValNative command, status: %d", status);
+ }
+}
+
+static void getPlayerApplicationSettingValuesNative(JNIEnv *env, jobject object, jbyte num_attrib, jbyteArray attrib_ids) {
+ bt_status_t status;
+ uint8_t *pAttrs = NULL;
+ int i;
+ jbyte *attr;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ pAttrs = new uint8_t[num_attrib];
+ if (!pAttrs) {
+ ALOGE("getPlayerApplicationSettingValuesNative: not have enough memeory");
+ return;
+ }
+ attr = env->GetByteArrayElements(attrib_ids, NULL);
+ if (!attr) {
+ delete[] pAttrs;
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ for (i = 0; i < num_attrib; ++i) {
+ pAttrs[i] = (uint8_t)attr[i];
+ }
+ if (i < num_attrib) {
+ delete[] pAttrs;
+ env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_player_app_setting_cmd((uint8_t)num_attrib, pAttrs))
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getPlAppSettValNative command, status: %d", status);
+ }
+ delete[] pAttrs;
+ env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+}
+
+static void setPlayerApplicationSettingValuesNative(JNIEnv *env, jobject object, jbyte num_attrib, jbyteArray attrib_ids,
+ jbyteArray attrib_val) {
+ bt_status_t status;
+ uint8_t *pAttrs = NULL;
+ uint8_t *pAttrsVal = NULL;
+ int i;
+ jbyte *attr;
+ jbyte *attr_val;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ pAttrs = new uint8_t[num_attrib];
+ pAttrsVal = new uint8_t[num_attrib];
+ if ((!pAttrs) ||(!pAttrsVal)) {
+ 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((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);
+}
+
+static void registerNotificationNative(JNIEnv *env, jobject object, jbyte event_id, jint value ) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->register_notification_cmd((uint8_t)event_id,(uint32_t)value))
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending registerNotificationNative command, status: %d", status);
+ }
+}
+
+static void getElementAttributeNative(JNIEnv *env, jobject object, jbyte num_attrib, jint attrib_id) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_element_attribute_cmd((uint8_t)num_attrib,
+ (uint32_t)attrib_id))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getElementAttributeNative command, status: %d", status);
+ }
+}
+
+static void getPlayStatusNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_play_status_cmd())!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getPlayStatusNative command, status: %d", status);
+ }
+}
+
+static void sendAbsVolRspNative(JNIEnv *env, jobject object, jint abs_vol) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->send_abs_vol_rsp((uint8_t)abs_vol))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending sendAbsVolRspNative command, status: %d", status);
+ }
+}
+
+static void sendRegisterAbsVolRspNative(JNIEnv *env, jobject object, jbyte rsp_type, jint abs_vol) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->send_register_abs_vol_rsp((uint8_t)rsp_type,
+ (uint8_t)abs_vol))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending sendRegisterAbsVolRspNative command, status: %d", status);
+ }
+}
+
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},
+ {"getCapabilitiesNative", "(I)V",(void *) getCapabilitiesNative},
+ {"listPlayerApplicationSettingAttributeNative", "()V",
+ (void *) listPlayerApplicationSettingAttributeNative},
+ {"listPlayerApplicationSettingValueNative", "(B)V",
+ (void *) listPlayerApplicationSettingValueNative},
+ {"getPlayerApplicationSettingValuesNative", "(B[B)V",
+ (void *) getPlayerApplicationSettingValuesNative},
+ {"setPlayerApplicationSettingValuesNative", "(B[B[B)V",
+ (void *) setPlayerApplicationSettingValuesNative},
+ {"registerNotificationNative", "(BI)V",
+ (void *) registerNotificationNative},
+ {"getElementAttributeNative", "(BI)V",(void *) getElementAttributeNative},
+ {"getPlayStatusNative", "()V",(void *) getPlayStatusNative},
+ {"sendAbsVolRspNative", "(I)V",(void *) sendAbsVolRspNative},
+ {"sendRegisterAbsVolRspNative", "(BI)V",(void *) sendRegisterAbsVolRspNative},
};
int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b933df728..19d54aef0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -23,6 +23,13 @@
-->
<string name="permlab_bluetoothShareManager">Access download manager.</string>
<!--
+ This is the short description of a permission associated with the
+ Bluetooth Avrcp Data Manager. It is displayed as part of the description of
+ any application that was granted that permission. This specific
+ permission controls access to the Bluetooth Avrcp Data Manager.
+ -->
+ <string name="permlab_bluetoothAvrcpDataManager">Access avrcp metadata.</string>
+ <!--
This is the long description of a permission associated with the
Android Download Manager. It is displayed as part of the description
of any application that was granted that permission. This specific
@@ -31,6 +38,14 @@
-->
<string name="permdesc_bluetoothShareManager">Allows the app to access the
BluetoothShare manager and use it to transfer files. </string>
+ <!--
+ This is the long description of a permission associated with the
+ Bluetooth Avrcp Data Manager. It is displayed as part of the description of
+ any application that was granted that permission. This specific
+ permission controls access to the Bluetooth Avrcp Data Manager.
+ -->
+ <string name="permdesc_bluetoothAvrcpDataManager">Allows the app to access the
+ BluetoothAvrcpData manager and use it to display on UI. </string>
<string name="permlab_bluetoothWhitelist">Whitelist bluetooth device access.</string>
diff --git a/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
index eac52bc0a..b99dd36c7 100644
--- a/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
@@ -182,6 +182,10 @@ final class A2dpSinkStateMachine extends StateMachine {
" 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();
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
index ed426ecef..e880fdfa9 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
@@ -1,4 +1,6 @@
/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,23 +20,35 @@ package com.android.bluetooth.avrcp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpInfo;
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.content.ContentValues;
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 android.net.Uri;
+import android.database.Cursor;
+import com.android.bluetooth.avrcp.Avrcp.Metadata;
+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 android.util.Log;
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
/**
* Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
* @hide
@@ -43,10 +57,279 @@ public class AvrcpControllerService extends ProfileService {
private static final boolean DBG = false;
private static final String TAG = "AvrcpControllerService";
+ //private Context mContext;
+/*
+ * Messages handled by mHandler
+ */
private static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+ private static final int MESSAGE_GET_SUPPORTED_COMPANY_ID = 2;
+ private static final int MESSAGE_GET_SUPPORTED_EVENTS = 3;
+ private static final int MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB = 4;
+ private static final int MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES = 5;
+ private static final int MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS = 6;
+ private static final int MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS = 7;
+ private static final int MESSAGE_GET_ELEMENT_ATTRIBUTE = 8;
+ private static final int MESSAGE_GET_PLAY_STATUS = 9;
+ private static final int MESSAGE_DEINIT_AVRCP_DATABASE = 10;
+ private static final int MESSAGE_REGISTER_NOTIFICATION = 11;
+
+ private static final int MESSAGE_CMD_TIMEOUT = 100;
+ /* Timeout Defined as per Spec */
+ private static final int TIMEOUT_MTP = 1000;
+ private static final int TIMEOUT_MTC = 200;
+ private static final int TIMEOUT_RCP = 100;
+ /* we add 1000ms extra */
+ private static final int MSG_TIMEOUT_MTP = 1000 + TIMEOUT_MTP;
+ private static final int MSG_TIMEOUT_MTC = 1000 + TIMEOUT_MTC;
+ private static final int MSG_TIMEOUT_RCP = 1000 + TIMEOUT_RCP;
+ private static final int GET_PLAY_STATUS_INTERVAL = 5000; // every 5sec
+
+
+ private static final int MESSAGE_PROCESS_SUPPORTED_COMPANY_ID = 1002;
+ private static final int MESSAGE_PROCESS_SUPPORTED_EVENTS = 1003;
+ private static final int MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_ATTRIB = 1004;
+ private static final int MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_VALUES = 1005;
+ private static final int MESSAGE_PROCESS_CURRENT_PLAYER_APPLICATION_SETTINGS = 1006;
+ private static final int MESSAGE_PROCESS_ELEMENT_ATTRIBUTE = 1008;
+ private static final int MESSAGE_PROCESS_PLAY_STATUS = 1009;
+ private static final int MESSAGE_REGISTER_PLAYBACK_STATUS_CHANGED = 1010;
+ private static final int MESSAGE_REGISTER_TRACK_CHANGED = 1011;
+ private static final int MESSAGE_REGISTER_PLAYBACK_POS_CHANGED = 1012;
+ private static final int MESSAGE_REGISTER_PLAYER_APPLICATION_SETTINGS_CHANGED = 1013;
+ private static final int MESSAGE_REGISTER_PLAYER_APPLICATION_VOLUME_CHANGED = 1014;
+ private static final int MESSAGE_PROCESS_NOTIFICATION_RESPONSE = 1015;
+ private static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 1016;
+ private static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_REQUEST = 1017;
+
+ private static final int MESSAGE_PROCESS_RC_FEATURES = 1100;
+ private static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 1200;
+ private static final int ABORT_FETCH_ELEMENT_ATTRIBUTE = 4000 + MESSAGE_GET_ELEMENT_ATTRIBUTE;
+
+
+/*
+ * Capability IDs for GetCapabilities
+ */
+ private static final int COMPANY_ID = 2;
+ private static final int EVENTS_SUPPORTED = 3;
+
+ private static final int NOTIFICATION_RSP_TYPE_INTERIM = 0x0f;
+ private static final int NOTIFICATION_RSP_TYPE_CHANGED = 0x0d;
+
+ private static final int PLAYBACK_POS_INTERVAL = 1; // time in seconds
+/*
+ * Constants for Events Supported
+ */
+ private static final byte EVENT_NOTIFICAION_ID_NONE = 0x00;
+ private static final byte EVENT_PLAYBACK_STATUS_CHANGED = 0x01;
+ private static final byte EVENT_TRACK_CHANGED = 0x02;
+ private static final byte EVENT_PLAYBACK_POS_CHANGED = 0x05;
+ private static final byte EVENT_PLAYER_APPLICATION_SETTINGS_CHANGED = 0x08;
+ private static final byte EVENT_VOLUME_CHANGED = 0x0d;
+/*
+ * Timeout values for Events Supported
+ */
+ private static final int MESSAGE_TIMEOUT_PLAYBACK_STATUS_CHANGED = 2001;
+ private static final int MESSAGE_TIMEOUT_TRACK_CHANGED = 2002;
+ private static final int MESSAGE_TIMEOUT_PLAYBACK_POS_CHNAGED = 2005;
+ private static final int MESSAGE_TIMEOUT_APPL_SETTINGS_CHANGED = 2008;
+ private static final int MESSAGE_TIMEOUT_VOLUME_CHANGED = 2013;
+
+
+ private static final byte ATTRIB_EQUALIZER_STATUS = 0x01;
+ private static final byte ATTRIB_REPEAT_STATUS = 0x02;
+ private static final byte ATTRIB_SHUFFLE_STATUS = 0x03;
+ private static final byte ATTRIB_SCAN_STATUS = 0x04;
+
+/*
+ * EQUALIZER State Values
+ */
+ private static final byte EQUALIZER_STATUS_OFF = 0x01;
+ private static final byte EQUALIZER_STATUS_ON = 0x02;
+
+/*
+ * REPEAT State Values
+ */
+ private static final byte REPEAT_STATUS_OFF = 0x01;
+ private static final byte REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+ private static final byte REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+ private static final byte REPEAT_STATUS_GROUP_REPEAT = 0x04;
+/*
+ * SHUFFLE State Values
+ */
+ private static final byte SHUFFLE_STATUS_OFF = 0x01;
+ private static final byte SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+ private static final byte SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+
+/*
+ * Scan State Values
+ */
+ private static final byte SCAN_STATUS_OFF = 0x01;
+ private static final byte SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
+ private static final byte SCAN_STATUS_GROUP_SCAN = 0x03;
+
+/*
+ * Play State Values
+ */
+ private static final byte PLAY_STATUS_STOPPED = 0x00;
+ private static final byte PLAY_STATUS_PLAYING = 0x01;
+ private static final byte PLAY_STATUS_PAUSED = 0x02;
+ private static final byte PLAY_STATUS_FWD_SEEK = 0x03;
+ private static final byte PLAY_STATUS_REV_SEEK = 0x04;
+/*
+ * values possible for notify_state
+ */
+ private static final byte NOTIFY_NOT_NOTIFIED = 0x01;
+ private static final byte NOTIFY_INTERIM_EXPECTED = 0x02;
+ private static final byte NOTIFY_CHANGED_EXPECTED = 0x03;
+ /*
+ * For Absolute volume we are in a TG role.
+ * Where we will receive notification and we have
+ * to send an interim/Chnaged response. So keeping
+ * those states seperate.
+ * Also we shld not send Chnaged response if change
+ * in volume is because of setabsvol command send from
+ * remote.
+ */
+ private static final byte NOTIFY_NOT_REGISTERED = 0x01;
+ private static final byte NOTIFY_RSP_INTERIM_SENT = 0x02;
+ private static final byte NOTIFY_RSP_ABS_VOL_DEFERRED = 0x03;
+
+/*
+ * Constants for GetElement Attribute
+ */
+ private static final int MEDIA_ATTRIBUTE_ALL = 0x00;
+ private static final int MEDIA_ATTRIBUTE_TITLE = 0x01;
+ private static final int MEDIA_ATTRIBUTE_ARTIST_NAME = 0x02;
+ private static final int MEDIA_ATTRIBUTE_ALBUM_NAME = 0x03;
+ private static final int MEDIA_ATTRIBUTE_TRACK_NUMBER = 0x04;
+ private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
+ private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
+ private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+
+ private static final int MEDIA_PLAYSTATUS_ALL = 0x08;
+ private static final int MEDIA_PLAYSTATUS_SONG_TOTAL_LEN = 0x09;
+ private static final int MEDIA_PLAYSTATUS_SONG_CUR_POS = 0x0a;
+ private static final int MEDIA_PLAYSTATUS_SONG_PLAY_STATUS = 0x0b;
+ private static final int MEDIA_PLAYER_APPLICAITON_SETTING = 0x0c;
+
+ /*
+ * Timeout values for GetElement Attribute
+ */
+ private static final int GET_ELEMENT_ATTR_TIMEOUT_BASE = 3000;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_TITLE =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_TITLE;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_ARTIST_NAME =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_ARTIST_NAME;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_ALBUM_NAME =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_ALBUM_NAME;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_TRACK_NUMBER =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_TRACK_NUMBER;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_TOTAL_TRACK_NUMBER =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_GENRE =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_GENRE;
+ private static final int MESSAGE_TIMEOUT_ATTRIBUTE_PLAYING_TIME =
+ GET_ELEMENT_ATTR_TIMEOUT_BASE + MEDIA_ATTRIBUTE_PLAYING_TIME;
+
+ private static final int ATTRIBUTE_FETCH_CONTINUE = 0;
+ private static final int ATTRIBUTE_FETCH_FRESH = 1; // discard all if we are already fetching
+ private static final int ATTRIBUTE_FETCH_SKIP = 3;// discard only current one
+
+ /* AVRCP Rsp Status */
+ private static final int AVRC_RSP_NOT_IMPL = 8;
+ private static final int AVRC_RSP_ACCEPT = 9;
+ private static final int AVRC_RSP_REJ = 10;
+ private static final int AVRC_RSP_IN_TRANS = 11;
+ private static final int AVRC_RSP_IMPL_STBL = 12;
+ private static final int AVRC_RSP_CHANGED = 13;
+ private static final int AVRC_RSP_INTERIM = 15;
+
+ /*
+ * AVRCP Key event
+ */
+ public static final int AVRC_ID_PLAY = 0x44;
+ public static final int AVRC_ID_PAUSE = 0x46;
+ public static final int AVRC_ID_STOP = 0x45;
+ public static final int AVRC_ID_FF = 0x49;
+ public static final int AVRC_ID_REWIND = 0x48;
+ public static final int AVRC_ID_FORWARD = 0x4B;
+ public static final int AVRC_ID_BACKWARD = 0x4C;
+ public static final int AVRC_ID_VOL_UP = 0x41;
+ public static final int AVRC_ID_VOL_DOWN = 0x42;
+
+ public static final int ABS_VOL_BASE = 127;
+/*
+ * Constant Variables Defined
+ */
+ private final int BTSIG_COMPANY_ID = 0x001958;
+
+ public static final int BTRC_FEAT_METADATA = 0x01;
+ public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
+ public static final int BTRC_FEAT_BROWSE = 0x04;
+
+ private class PlayerSettings
+ {
+ public byte attr_Id;
+ public byte attr_val;
+ public byte [] supported_values;
+ };
+ private class NotifyEvents
+ {
+ public byte notify_event_id; // these values will be one of Supported Events
+ public byte notify_state; // Current State of Notification
+ };
+ private class Metadata {
+ private String artist;
+ private String trackTitle;
+ private String albumTitle;
+ private String genre;
+ private long trackNum;
+ private long totalTrackNum;
+ private byte playStatus;
+ private long playTime;
+ private long totalTrackLen;
+ private int attributesFetchedId;
+
+ public Metadata() {
+ resetMetaData();
+ }
+ public void resetMetaData() {
+ artist = BluetoothAvrcpInfo.ARTIST_NAME_INVALID;
+ trackTitle = BluetoothAvrcpInfo.TITLE_INVALID;
+ albumTitle = BluetoothAvrcpInfo.ALBUM_NAME_INVALID;
+ genre = BluetoothAvrcpInfo.GENRE_INVALID;
+ trackNum = BluetoothAvrcpInfo.TRACK_NUM_INVALID;
+ totalTrackNum = BluetoothAvrcpInfo.TOTAL_TRACKS_INVALID;
+ playStatus = PLAY_STATUS_STOPPED;
+ playTime = BluetoothAvrcpInfo.PLAYING_TIME_INVALID;
+ totalTrackLen = BluetoothAvrcpInfo.TOTAL_TRACK_TIME_INVALID;
+ attributesFetchedId = -1; // id of the attribute being fetched, initialized by -1
+ }
+ public String toString() {
+ return "Metadata [artist=" + artist + " trackTitle= " + trackTitle + " albumTitle= " +
+ albumTitle + " genre= " +genre+" trackNum= "+Long.toString(trackNum) + " cur_time: "+
+ Long.toString(playTime) + " total_time = "+ Long.toString(totalTrackLen) + "]";
+ }
+ };
+
+ private final class RemoteAvrcpData {
+ private ArrayList <Integer> mCompanyIDSupported; // company ID
+ private ArrayList <Byte> mEventsSupported;
+ private ArrayList <PlayerSettings> mSupportedApplicationSettingsAttribute;
+ private ArrayList <NotifyEvents> mNotifyEvent;
+ private Metadata mMetadata;
+ int mRemoteFeatures;
+ int absVolNotificationState;
+ int playerSettingAttribIdFetch;
+ };
+
+ RemoteAvrcpData mRemoteData;
+ int[] requestedElementAttribs;
private AvrcpMessageHandler mHandler;
private static AvrcpControllerService sAvrcpControllerService;
+ private static AudioManager mAudioManager;
+ private static boolean mDbInitialized = false;
private final ArrayList<BluetoothDevice> mConnectedDevices
= new ArrayList<BluetoothDevice>();
@@ -64,6 +347,7 @@ public class AvrcpControllerService extends ProfileService {
}
protected IProfileServiceBinder initBinder() {
+ Log.d(TAG," initBinder Called ");
return new BluetoothAvrcpControllerBinder(this);
}
@@ -72,25 +356,83 @@ public class AvrcpControllerService extends ProfileService {
thread.start();
Looper looper = thread.getLooper();
mHandler = new AvrcpMessageHandler(looper);
-
+ mRemoteData = null;
setAvrcpControllerService(this);
+ mAudioManager = (AudioManager)sAvrcpControllerService.
+ getSystemService(Context.AUDIO_SERVICE);
+ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ registerReceiver(mBroadcastReceiver, filter);
return true;
}
protected boolean stop() {
+ unregisterReceiver(mBroadcastReceiver);
+ try {
+ deinitDatabase();
+ if (mRemoteData != null) {
+ mRemoteData.mCompanyIDSupported.clear();
+ mRemoteData.mEventsSupported.clear();
+ mRemoteData.mMetadata.resetMetaData();
+ mRemoteData.mNotifyEvent.clear();
+ mRemoteData.mSupportedApplicationSettingsAttribute.clear();
+ mRemoteData.absVolNotificationState = NOTIFY_NOT_REGISTERED;
+ mRemoteData.mRemoteFeatures = 0;
+ Log.d(TAG," RC_features, STOP " + mRemoteData.mRemoteFeatures);
+ mRemoteData.playerSettingAttribIdFetch = 0;
+ mRemoteData = null;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Cleanup failed", e);
+ }
return true;
}
-
+ 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 ((mRemoteData == null)
+ ||((mRemoteData.mRemoteFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) == 0)
+ ||(mConnectedDevices.isEmpty()))
+ return;
+ if(mRemoteData.absVolNotificationState == NOTIFY_RSP_INTERIM_SENT) {
+ int maxVol = mAudioManager.
+ getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.
+ getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex*ABS_VOL_BASE)/maxVol);
+ byte rspType = NOTIFICATION_RSP_TYPE_CHANGED;
+ Log.d(TAG," Abs Vol Notify Rsp Changed val = "+ percentageVol);
+ mRemoteData.absVolNotificationState = NOTIFY_NOT_REGISTERED;
+ sendRegisterAbsVolRspNative(rspType,percentageVol);
+ }
+ else if (mRemoteData.absVolNotificationState == NOTIFY_RSP_ABS_VOL_DEFERRED) {
+ Log.d(TAG," Don't Complete Notification Rsp. ");
+ mRemoteData.absVolNotificationState = NOTIFY_RSP_INTERIM_SENT;
+ }
+ }
+ }
+ }
+ }
+ };
protected boolean cleanup() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
Looper looper = mHandler.getLooper();
if (looper != null) looper.quit();
+ if (looper != null) {
+ looper.quit();
+ }
}
-
clearAvrcpControllerService();
cleanupNative();
-
return true;
}
@@ -158,12 +500,351 @@ public class AvrcpControllerService extends ProfileService {
if (device == null) {
throw new NullPointerException("device == null");
}
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(MESSAGE_SEND_PASS_THROUGH_CMD,
+ if (!(mConnectedDevices.contains(device))) {
+ Log.d(TAG," Device does not match");
+ return;
+ }
+ if ((mRemoteData == null)||(mRemoteData.mMetadata == null)) {
+ Log.d(TAG," Device connected but PlayState not present ");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_SEND_PASS_THROUGH_CMD,
+ keyCode, keyState, device);
+ mHandler.sendMessage(msg);
+ return;
+ }
+ boolean sendCommand = false;
+ switch(keyCode) {
+ case AVRC_ID_PLAY:
+ sendCommand = (mRemoteData.mMetadata.playStatus == PLAY_STATUS_STOPPED)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_PAUSED);
+ break;
+ case AVRC_ID_PAUSE:
+ sendCommand = (mRemoteData.mMetadata.playStatus == PLAY_STATUS_PLAYING)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_FWD_SEEK)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_STOPPED)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_REV_SEEK);
+ break;
+ case AVRC_ID_STOP:
+ sendCommand = (mRemoteData.mMetadata.playStatus == PLAY_STATUS_PLAYING)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_FWD_SEEK)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_REV_SEEK)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_STOPPED)||
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_PAUSED);
+ break;
+ case AVRC_ID_VOL_DOWN:
+ case AVRC_ID_VOL_UP:
+ case AVRC_ID_BACKWARD:
+ case AVRC_ID_FORWARD:
+ case AVRC_ID_FF:
+ case AVRC_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(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 void getMetaData(int[] attributeIds) {
+ Log.d(TAG, "num getMetaData = "+ attributeIds.length);
+ if (mRemoteData == null) {
+ return;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ /*
+ * Check if GetElementAttribute is in progress
+ * Check if it already does not contain all Attributes
+ * If it is, append any missing attribute
+ */
+ if ((mRemoteData.mMetadata.attributesFetchedId != -1) &&
+ (requestedElementAttribs != null)&&
+ (requestedElementAttribs.length < 7)) {
+
+ int currAttributeId =
+ requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId];
+ if (mHandler.hasMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + currAttributeId)) {
+ mHandler.removeMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + currAttributeId);
+ Log.d(TAG," Timeout CMD dequeued ID " + currAttributeId);
+ }
+ mHandler.sendEmptyMessage(ABORT_FETCH_ELEMENT_ATTRIBUTE);
+ requestedElementAttribs = Arrays.copyOf(attributeIds, attributeIds.length);
+ return;
+ }
+ else if ((attributeIds.length >= 0)&&(attributeIds[0] != MEDIA_ATTRIBUTE_ALL)) {
+ /* Subset sent by App, use them */
+ requestedElementAttribs = Arrays.copyOf(attributeIds, attributeIds.length);
+ }
+ else {
+ /* Use SuperSet */
+ requestedElementAttribs = new int [7];
+ for (int xx = 0; xx < 7; xx++)
+ requestedElementAttribs[xx] = xx+1;
+ }
+ Arrays.sort(requestedElementAttribs);
+ boolean mMetaDataPresent = true;
+ for (int attributeId: requestedElementAttribs) {
+ if (!isMetaDataPresent(attributeId)) {
+ mMetaDataPresent = false;
+ break;
+ }
+ }
+ Log.d(TAG," MetaDataPresent " + mMetaDataPresent);
+ if(mMetaDataPresent)
+ triggerNotification();
+ else
+ mHandler.sendEmptyMessage(MESSAGE_GET_ELEMENT_ATTRIBUTE);
+ }
+ public void getPlayStatus(int[] playStatusIds) {
+ if (DBG) Log.d(TAG, "num getPlayStatus ID = "+ playStatusIds.length);
+ if (mRemoteData == null) {
+ return;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int[] getApRequestedPlayStatusAttrib;
+ if ((playStatusIds.length >= 0)&&(playStatusIds[0] != MEDIA_PLAYSTATUS_ALL)) {
+ /* Subset sent by App, use them */
+ getApRequestedPlayStatusAttrib = Arrays.copyOf(playStatusIds, playStatusIds.length);
+ }
+ else {
+ /* Use SuperSet */
+ getApRequestedPlayStatusAttrib = new int [3];
+ for (int xx = 0; xx < 3; xx++)
+ getApRequestedPlayStatusAttrib[xx] = 9 + xx;
+ }
+ Arrays.sort(getApRequestedPlayStatusAttrib);
+ boolean mMetaDataPresent = true;
+ for (int attributeId: getApRequestedPlayStatusAttrib) {
+ if (!isMetaDataPresent(attributeId)) {
+ mMetaDataPresent = false;
+ break;
+ }
+ }
+ if(mMetaDataPresent)
+ triggerNotification();
+ else {
+ Log.d(TAG," Metadata not present");
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAY_STATUS);
+ }
+ }
+ public void getPlayerApplicationSetting() {
+ if (DBG) Log.d(TAG, "getPlayerApplicationSetting ");
+ if (mRemoteData == null) {
+ return;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if(isMetaDataPresent(MEDIA_PLAYER_APPLICAITON_SETTING)) {
+ triggerNotification();
+ }
+ else {
+ Log.d(TAG," Metadata not present, fetch it");
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ }
+ }
+ public void setPlayerApplicationSetting(int attributeId, int attributeVal) {
+ if (mRemoteData == null) {
+ return;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS,
+ attributeId, attributeVal);
mHandler.sendMessage(msg);
}
+ public BluetoothAvrcpInfo getSupportedPlayerAppSetting(BluetoothDevice device) {
+ if((mRemoteData == null)||(mRemoteData.mSupportedApplicationSettingsAttribute.isEmpty())
+ ||(!mConnectedDevices.contains(device)))
+ return null;
+ byte[] attribIds = new byte[mRemoteData.mSupportedApplicationSettingsAttribute.size()];
+ byte[] numAttribVals = new byte[mRemoteData.mSupportedApplicationSettingsAttribute.size()];
+ ArrayList<Byte> supportedVals = new ArrayList<Byte>();
+ int index = 0;
+ for (PlayerSettings plSetting: mRemoteData.mSupportedApplicationSettingsAttribute) {
+ attribIds[index] = plSetting.attr_Id;
+ numAttribVals[index] = Integer.valueOf(plSetting.supported_values.length).byteValue();
+ for (int xx = 0; xx < numAttribVals[index]; xx++)
+ supportedVals.add(plSetting.supported_values[xx]);
+ index++;
+ }
+ byte[] supportedPlSettingsVals = new byte[supportedVals.size()];
+ for (int zz = 0; zz < supportedVals.size(); zz++)
+ supportedPlSettingsVals[zz] = supportedVals.get(zz);
+ if ((attribIds == null)||(numAttribVals == null)||(supportedPlSettingsVals == null)||
+ (attribIds.length == 0)||(numAttribVals.length == 0))
+ return null;
+ BluetoothAvrcpInfo btAvrcpMetaData = new BluetoothAvrcpInfo(attribIds,
+ numAttribVals, supportedPlSettingsVals);
+ return btAvrcpMetaData;
+ }
+ public int getSupportedFeatures(BluetoothDevice device) {
+ if (!mConnectedDevices.contains(device)||(mRemoteData == null)) {
+ Log.e(TAG," req Device " + device + " Internal List " + mConnectedDevices.get(0));
+ Log.e(TAG," remoteData " + mRemoteData);
+ if (mRemoteData != null)
+ Log.e(TAG," getSupportedFeatures returning from here "+ mRemoteData.mRemoteFeatures);
+ return 0;
+ }
+ Log.d(TAG," getSupportedFeatures returning " + mRemoteData.mRemoteFeatures);
+ return mRemoteData.mRemoteFeatures;
+ }
+ private void triggerNotification() {
+ Uri avrcpDataUri = BluetoothAvrcpInfo.CONTENT_URI;
+ sAvrcpControllerService.getContentResolver().notifyChange(avrcpDataUri, null);
+ }
+ private boolean isMetaDataPresent(int attributeId) {
+ Uri avrcpDataUri = BluetoothAvrcpInfo.CONTENT_URI;
+ Cursor cursor = sAvrcpControllerService.getContentResolver().query(avrcpDataUri,
+ null, null, null,BluetoothAvrcpInfo._ID);
+ if ((cursor == null) || (!cursor.moveToFirst())) {
+ Log.d(TAG," isMetaDataPresent cursor not valid, returing");
+ return false;
+ }
+ int index = 0;
+ boolean metaDataPresent = false;
+ switch(attributeId)
+ {
+ case MEDIA_ATTRIBUTE_ALBUM_NAME:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.ALBUM_NAME);
+ if(cursor.getString(index) !=
+ BluetoothAvrcpInfo.ALBUM_NAME_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_ARTIST_NAME:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.ARTIST_NAME);
+ if(cursor.getString(index) !=
+ BluetoothAvrcpInfo.ARTIST_NAME_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_GENRE:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.GENRE);
+ if(cursor.getString(index) !=
+ BluetoothAvrcpInfo.GENRE_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_TITLE:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TITLE);
+ if(cursor.getString(index) !=
+ BluetoothAvrcpInfo.TITLE_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_PLAYSTATUS_SONG_CUR_POS:
+ case MEDIA_ATTRIBUTE_PLAYING_TIME:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.PLAYING_TIME);
+ if(cursor.getInt(index) !=
+ BluetoothAvrcpInfo.PLAYING_TIME_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TOTAL_TRACKS);
+ if(cursor.getInt(index) !=
+ BluetoothAvrcpInfo.TOTAL_TRACKS_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TRACK_NUM);
+ if(cursor.getInt(index) !=
+ BluetoothAvrcpInfo.TRACK_NUM_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_ATTRIBUTE_ALL: // check all attribute
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TRACK_NUM);
+ if(cursor.getInt(index) ==
+ BluetoothAvrcpInfo.TRACK_NUM_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TOTAL_TRACKS);
+ if(cursor.getInt(index) ==
+ BluetoothAvrcpInfo.TOTAL_TRACKS_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.PLAYING_TIME);
+ if(cursor.getInt(index) ==
+ BluetoothAvrcpInfo.PLAYING_TIME_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TITLE);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.TITLE_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.GENRE);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.GENRE_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.ARTIST_NAME);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.ARTIST_NAME_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.ALBUM_NAME);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.ALBUM_NAME_INVALID)
+ break;
+ metaDataPresent = true;
+ break;
+ case MEDIA_PLAYSTATUS_SONG_PLAY_STATUS:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.PLAY_STATUS);
+ if(cursor.getString(index) !=
+ BluetoothAvrcpInfo.PLAY_STATUS_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_PLAYSTATUS_SONG_TOTAL_LEN:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TOTAL_TRACK_TIME);
+ if(cursor.getInt(index) !=
+ BluetoothAvrcpInfo.TOTAL_TRACK_TIME_INVALID)
+ metaDataPresent = true;
+ break;
+ case MEDIA_PLAYSTATUS_ALL:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.PLAY_STATUS);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.PLAY_STATUS_INVALID)
+ break;
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.TOTAL_TRACK_TIME);
+ if(cursor.getInt(index) ==
+ BluetoothAvrcpInfo.TOTAL_TRACK_TIME_INVALID)
+ break;
+
+ metaDataPresent = true;
+ break;
+ case MEDIA_PLAYER_APPLICAITON_SETTING:
+ boolean plSettingSupported = true;
+ for (PlayerSettings plSetting: mRemoteData.mSupportedApplicationSettingsAttribute) {
+ switch(plSetting.attr_Id)
+ {
+ case ATTRIB_REPEAT_STATUS:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.REPEAT_STATUS);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.REPEAT_STATUS_INVALID)
+ plSettingSupported = false;
+ break;
+ case ATTRIB_EQUALIZER_STATUS:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.EQUALIZER_STATUS);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.EQUALIZER_STATUS_INVALID)
+ plSettingSupported = false;
+ break;
+ case ATTRIB_SCAN_STATUS:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.SCAN_STATUS);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.SCAN_STATUS_INVALID)
+ plSettingSupported = false;
+ break;
+ case ATTRIB_SHUFFLE_STATUS:
+ index = cursor.getColumnIndex(BluetoothAvrcpInfo.SHUFFLE_STATUS);
+ if(cursor.getString(index) ==
+ BluetoothAvrcpInfo.SHUFFLE_STATUS_INVALID)
+ plSettingSupported = false;
+ break;
+ }
+ if(!plSettingSupported)
+ break;
+ }
+ metaDataPresent = plSettingSupported;
+ break;
+ }
+ cursor.close();
+ Log.d(TAG," returning " + metaDataPresent + "for attrib " + attributeId);
+ return metaDataPresent;
+ }
//Binder object: Must be static class or memory leak may occur
private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
@@ -214,8 +895,733 @@ public class AvrcpControllerService extends ProfileService {
if (service == null) return;
service.sendPassThroughCmd(device, keyCode, keyState);
}
+
+ public void getMetaData(int[] attributeIds) {
+ Log.v(TAG,"Binder Call: num getMetaData ID = "+ attributeIds.length);
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.getMetaData(attributeIds);
+ }
+ public void getPlayStatus(int[] playStatusIds) {
+ Log.v(TAG,"Binder Call: num getPlayStatus ID = "+ playStatusIds.length);
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.getPlayStatus(playStatusIds);
+ }
+ public void getPlayerApplicationSetting() {
+ Log.v(TAG,"Binder Call: getPlayerApplicationSetting ");
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.getPlayerApplicationSetting();
+ }
+ public void setPlayerApplicationSetting(int attributeId, int attributeVal) {
+ Log.v(TAG,"Binder Call: setPlayerApplicationSetting ID = "
+ + attributeId +" attributeVal ="+ attributeVal);
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.setPlayerApplicationSetting(attributeId, attributeVal);
+ }
+ public BluetoothAvrcpInfo getSupportedPlayerAppSetting(BluetoothDevice device) {
+ Log.v(TAG,"Binder Call: getSupportedPlayerAppSetting Dev = " + device);
+ AvrcpControllerService service = getService();
+ if (service == null) return null;
+ return service.getSupportedPlayerAppSetting(device);
+ }
+ public int getSupportedFeatures(BluetoothDevice device) {
+ Log.v(TAG,"Binder Call: getSupportedFeatures Dev = " + device);
+ AvrcpControllerService service = getService();
+ if (service == null) return 0;
+ return service.getSupportedFeatures(device);
+ }
};
+ private void getSupportedCapabilities(int capability_id)
+ {
+ Message msg = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,0,0,capability_id);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ getCapabilitiesNative(capability_id);
+ }
+ private void getPlayerApplicationSettingsAttrib()
+ {
+ Message msg = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,
+ 0,0,MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ listPlayerApplicationSettingAttributeNative();
+ }
+ private void getCurrentPlayerApplicationSettingsValues()
+ {
+ int count = 0;
+ if ((mRemoteData == null)||
+ (mRemoteData.mSupportedApplicationSettingsAttribute == null)||
+ (mRemoteData.mSupportedApplicationSettingsAttribute.size() == 0)) {
+ Log.w(TAG," PlayerAppSettings not supporterd, returning");
+ return;
+ }
+ byte[] supported_attrib =
+ new byte[mRemoteData.mSupportedApplicationSettingsAttribute.size()];
+ byte numAttrib = Byte.valueOf((Integer.valueOf
+ (mRemoteData.mSupportedApplicationSettingsAttribute.size())).byteValue());
+ for (PlayerSettings plSetting: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ supported_attrib[count++] = plSetting.attr_Id;
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,
+ 0,0,MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ getPlayerApplicationSettingValuesNative(numAttrib,supported_attrib);
+ }
+ private void setCurrentPlayerApplicationSettingsValues(int attribId, int attribVal)
+ {
+ Message msg = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,
+ 0,0,MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ byte numAttrib = 1;
+ byte[] attributeId = new byte[1];
+ byte[] attributeVal = new byte[1];
+ attributeId[0] = (byte)attribId;
+ attributeVal[0] = (byte)attribVal;
+ setPlayerApplicationSettingValuesNative(numAttrib, attributeId, attributeVal);
+ }
+ private void getFurtherElementAttribute(int operationId)
+ {
+ if ((requestedElementAttribs == null)||(requestedElementAttribs.length == 0))
+ {
+ Log.d(TAG," Applicaiton has not yet requested element attributes");
+ return;
+ }
+ Log.d(TAG," getFurtherElementAttribute op_Id = "
+ + operationId + " requestedIdLen = " + requestedElementAttribs.length);
+ byte numAttrib = 1;
+ if (operationId == ATTRIBUTE_FETCH_FRESH)
+ mRemoteData.mMetadata.attributesFetchedId = 0; // reset fetched_id
+ else if ((operationId == ATTRIBUTE_FETCH_SKIP)||(operationId == ATTRIBUTE_FETCH_CONTINUE))
+ mRemoteData.mMetadata.attributesFetchedId += 1; // skip this one, fetch next
+ if (mRemoteData.mMetadata.attributesFetchedId >= requestedElementAttribs.length)
+ {
+ /*
+ * we reached to last attribute. Update database now
+ */
+ mRemoteData.mMetadata.attributesFetchedId = -1;
+ updateElementAttribute();
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAY_STATUS);
+ return;
+ }
+ Message msg = mHandler.obtainMessage(GET_ELEMENT_ATTR_TIMEOUT_BASE
+ + requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId]);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ Log.d(TAG," getElemAttrReq numAttr "+ numAttrib + " Id "
+ + requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId]);
+ getElementAttributeNative(numAttrib,
+ requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId]);
+ }
+ private void getFurtherPlayerSettingAttrib(int operationId)
+ {
+ Log.d(TAG," getFurtherPlayerSettingAttrib Id = " + operationId);
+ if (mRemoteData == null)
+ return;
+ if (operationId == ATTRIBUTE_FETCH_FRESH)
+ mRemoteData.playerSettingAttribIdFetch = 0;
+ else if(operationId == ATTRIBUTE_FETCH_SKIP)
+ mRemoteData.playerSettingAttribIdFetch ++;
+ int fetch_id = mRemoteData.playerSettingAttribIdFetch;
+ if (fetch_id >= mRemoteData.mSupportedApplicationSettingsAttribute.size())
+ {
+ Log.d(TAG," All Attrib Fetched " + fetch_id);
+ mHandler.sendEmptyMessage(MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ return;
+ }
+ Log.d(TAG," fetching_id = " + fetch_id);
+ Message msg = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,
+ 0,0,MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ listPlayerApplicationSettingValueNative(mRemoteData.
+ mSupportedApplicationSettingsAttribute.get(fetch_id).attr_Id);
+ }
+ /*
+ * Api to register notification.
+ * exemptNotificaionId = notificaiton ID for which notification will not be sent.
+ * in case of we have other notifications to be sent we will do nothing for
+ * exemptedID. In case exemptedId is the only one left we will send notification
+ * request for this one.
+ */
+ private void registerFurtherNotification(int exemptNotificaionId)
+ {
+ Log.d(TAG," ExemptedNotificationId " + exemptNotificaionId);
+ /*
+ * check through the list and send notification
+ * request if state is not waiting for changed
+ */
+ boolean notificationSent = false;
+ Message msg;
+ for (NotifyEvents notifyEvent: mRemoteData.mNotifyEvent)
+ {
+ Log.d(TAG," ID = " + notifyEvent.notify_event_id + " state = "
+ + notifyEvent.notify_state);
+ if (notifyEvent.notify_event_id == exemptNotificaionId)
+ continue;
+ if (notifyEvent.notify_state == NOTIFY_NOT_NOTIFIED)
+ {
+ notificationSent = true;
+ notifyEvent.notify_state = NOTIFY_INTERIM_EXPECTED;
+ Log.d(TAG," queing notificaiton request id " +
+ (2000+notifyEvent.notify_event_id));
+ msg = mHandler.obtainMessage(2000 + notifyEvent.notify_event_id);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ if (notifyEvent.notify_event_id == EVENT_PLAYBACK_POS_CHANGED)
+ registerNotificationNative(notifyEvent.notify_event_id, PLAYBACK_POS_INTERVAL);
+ else
+ registerNotificationNative(notifyEvent.notify_event_id, 0);
+ break;
+ }
+ }
+ if ((!notificationSent) && (exemptNotificaionId != EVENT_NOTIFICAION_ID_NONE))
+ {
+ /*
+ * exempted value was valid and only this was not notified.
+ */
+ for (NotifyEvents notifyEvent: mRemoteData.mNotifyEvent)
+ {
+ if (notifyEvent.notify_event_id == exemptNotificaionId)
+ {
+ notificationSent = true;
+ notifyEvent.notify_state = NOTIFY_INTERIM_EXPECTED;
+ msg = mHandler.obtainMessage(2000 + notifyEvent.notify_event_id);
+ mHandler.sendMessageDelayed(msg, MSG_TIMEOUT_MTP);
+ if (notifyEvent.notify_event_id == EVENT_PLAYBACK_POS_CHANGED)
+ registerNotificationNative(notifyEvent.notify_event_id,
+ PLAYBACK_POS_INTERVAL);
+ else
+ registerNotificationNative(notifyEvent.notify_event_id, 0);
+ break;
+ }
+ }
+ }
+ }
+ private void initializeDatabase()
+ {
+ if (mDbInitialized)
+ return;
+ ContentValues values = new ContentValues();
+ values.put(BluetoothAvrcpInfo.TRACK_NUM, BluetoothAvrcpInfo.TRACK_NUM_INVALID);
+ values.put(BluetoothAvrcpInfo.TITLE, BluetoothAvrcpInfo.TITLE_INVALID);
+ values.put(BluetoothAvrcpInfo.ARTIST_NAME, BluetoothAvrcpInfo.ARTIST_NAME_INVALID);
+ values.put(BluetoothAvrcpInfo.ALBUM_NAME, BluetoothAvrcpInfo.ALBUM_NAME_INVALID);
+ values.put(BluetoothAvrcpInfo.TOTAL_TRACKS, BluetoothAvrcpInfo.TOTAL_TRACKS_INVALID);
+ values.put(BluetoothAvrcpInfo.GENRE, BluetoothAvrcpInfo.GENRE_INVALID);
+ values.put(BluetoothAvrcpInfo.PLAYING_TIME, BluetoothAvrcpInfo.PLAYING_TIME_INVALID);
+ values.put(BluetoothAvrcpInfo.TOTAL_TRACK_TIME,
+ BluetoothAvrcpInfo.TOTAL_TRACK_TIME_INVALID);
+ values.put(BluetoothAvrcpInfo.PLAY_STATUS, BluetoothAvrcpInfo.PLAY_STATUS_INVALID);
+ values.put(BluetoothAvrcpInfo.REPEAT_STATUS, BluetoothAvrcpInfo.REPEAT_STATUS_INVALID);
+ values.put(BluetoothAvrcpInfo.SHUFFLE_STATUS, BluetoothAvrcpInfo.SHUFFLE_STATUS_INVALID);
+ values.put(BluetoothAvrcpInfo.SCAN_STATUS, BluetoothAvrcpInfo.SCAN_STATUS_INVALID);
+ values.put(BluetoothAvrcpInfo.EQUALIZER_STATUS,
+ BluetoothAvrcpInfo.EQUALIZER_STATUS_INVALID);
+ Cursor cursor = getContentResolver().query(BluetoothAvrcpInfo.CONTENT_URI, null, null, null,
+ BluetoothAvrcpInfo._ID);
+ if((cursor != null)&&(cursor.getCount() > 0)) {
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI, values, null, null);
+ Log.d(TAG," initializeDataBase num_rows_updated " + rowsUpdated);
+ mDbInitialized = true;
+ cursor.close();
+ return;
+ }
+ Uri contentUri = sAvrcpControllerService.getContentResolver().
+ insert(BluetoothAvrcpInfo.CONTENT_URI, values);
+ Log.d(TAG," InitializeDatabase uri " + contentUri);
+ mDbInitialized = true;
+ }
+ private void deinitDatabase()
+ {
+ mDbInitialized = false;
+ int rows_deleted = sAvrcpControllerService.getContentResolver().
+ delete(BluetoothAvrcpInfo.CONTENT_URI, null, null);
+ Log.d(TAG, " DeinitDatabase rows_deleted "+ rows_deleted);
+ }
+ private String getPlayStatusString(byte playStatus)
+ {
+ switch(playStatus)
+ {
+ case PLAY_STATUS_STOPPED:
+ return "STOPPED";
+ case PLAY_STATUS_PLAYING:
+ return "PLAYING";
+ case PLAY_STATUS_PAUSED:
+ return "PAUSED";
+ case PLAY_STATUS_FWD_SEEK:
+ return "FWD_SEEK";
+ case PLAY_STATUS_REV_SEEK:
+ return "REV_SEEK";
+ }
+ return BluetoothAvrcpInfo.PLAY_STATUS_INVALID;
+ }
+ private String getShuffleStatusString()
+ {
+ for (PlayerSettings plSettings: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSettings.attr_Id == ATTRIB_SHUFFLE_STATUS)
+ {
+ switch(plSettings.attr_val)
+ {
+ case SHUFFLE_STATUS_OFF:
+ return "SHUFFLE_OFF";
+ case SHUFFLE_STATUS_GROUP_SHUFFLE:
+ return "SHUFFLE_GROUP_SHUFFLE";
+ case SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
+ return "SHUFFLE_ALL_TRACK_SHUFFLE";
+ }
+ }
+ }
+ return BluetoothAvrcpInfo.SHUFFLE_STATUS_INVALID;
+ }
+ private String getScanStatusString()
+ {
+ for (PlayerSettings plSettings: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSettings.attr_Id == ATTRIB_SCAN_STATUS)
+ {
+ switch(plSettings.attr_val)
+ {
+ case SCAN_STATUS_OFF:
+ return "SCAN_OFF";
+ case SCAN_STATUS_GROUP_SCAN:
+ return "SCAN_GROUP_SCAN";
+ case SCAN_STATUS_ALL_TRACK_SCAN:
+ return "SCAN_ALL_TRACK_SCAN";
+ }
+ }
+ }
+ return BluetoothAvrcpInfo.SCAN_STATUS_INVALID;
+ }
+ private String getEqualizerStatusString()
+ {
+ for (PlayerSettings plSettings: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSettings.attr_Id == ATTRIB_EQUALIZER_STATUS)
+ {
+ switch(plSettings.attr_val)
+ {
+ case EQUALIZER_STATUS_OFF:
+ return "EQUALIZER_OFF";
+ case EQUALIZER_STATUS_ON:
+ return "EQUALIZER_ON";
+ }
+ }
+ }
+ return BluetoothAvrcpInfo.EQUALIZER_STATUS_INVALID;
+ }
+ private String getRepeatStatusString()
+ {
+ for (PlayerSettings plSettings: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSettings.attr_Id == ATTRIB_REPEAT_STATUS)
+ {
+ switch(plSettings.attr_val)
+ {
+ case REPEAT_STATUS_OFF:
+ return "REPEAT_OFF";
+ case REPEAT_STATUS_SINGLE_TRACK_REPEAT:
+ return "REPEAT_SINGLE_TRACK_REPEAT";
+ case REPEAT_STATUS_GROUP_REPEAT:
+ return "REPEAT_GROUP_REPEAT";
+ case REPEAT_STATUS_ALL_TRACK_REPEAT:
+ return "REPEAT_ALL_TRACK_REPEAT";
+ }
+ }
+ }
+ return BluetoothAvrcpInfo.REPEAT_STATUS_INVALID;
+ }
+
+ private void updateElementAttribute()
+ {
+ Log.d(TAG," updateElementAttribute " + mRemoteData.mMetadata.toString());
+ ContentValues values = new ContentValues();
+ values.put(BluetoothAvrcpInfo.TITLE, mRemoteData.mMetadata.trackTitle);
+ values.put(BluetoothAvrcpInfo.ARTIST_NAME, mRemoteData.mMetadata.artist);
+ values.put(BluetoothAvrcpInfo.ALBUM_NAME, mRemoteData.mMetadata.albumTitle);
+ values.put(BluetoothAvrcpInfo.TRACK_NUM, mRemoteData.mMetadata.trackNum);
+ values.put(BluetoothAvrcpInfo.TOTAL_TRACKS, mRemoteData.mMetadata.totalTrackNum);
+ values.put(BluetoothAvrcpInfo.GENRE, mRemoteData.mMetadata.genre);
+ values.put(BluetoothAvrcpInfo.PLAYING_TIME, mRemoteData.mMetadata.playTime);
+ values.put(BluetoothAvrcpInfo.TOTAL_TRACK_TIME, mRemoteData.mMetadata.totalTrackLen);
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI, values, null, null);
+ Log.d(TAG," updateElementAttribute num_rows_updated " + rowsUpdated);
+ }
+ private void updateTrackNum()
+ {
+ ContentValues values = new ContentValues();
+ values.put(BluetoothAvrcpInfo.TRACK_NUM, mRemoteData.mMetadata.trackNum);
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI, values, null, null);
+ Log.d(TAG," updateTrackNum num_rows_updated " + rowsUpdated);
+ }
+ private void updatePlayTime()
+ {
+ ContentValues values = new ContentValues();
+ values.put(BluetoothAvrcpInfo.PLAYING_TIME, mRemoteData.mMetadata.playTime);
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI, values, null, null);
+ Log.d(TAG," updatePlayTime num_rows_updated " + rowsUpdated);
+ }
+ private void updatePlayerApplicationSettings()
+ {
+ ContentValues values = new ContentValues();
+ values.put(BluetoothAvrcpInfo.REPEAT_STATUS, getRepeatStatusString());
+ values.put(BluetoothAvrcpInfo.SHUFFLE_STATUS, getShuffleStatusString());
+ values.put(BluetoothAvrcpInfo.SCAN_STATUS, getScanStatusString());
+ values.put(BluetoothAvrcpInfo.EQUALIZER_STATUS, getEqualizerStatusString());
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI,values, null, null);
+ Log.d(TAG," updatePlayerApplicationSettings num_rows_updated " + rowsUpdated);
+ }
+ private void updatePlayStatus()
+ {
+ ContentValues values = new ContentValues();
+ Log.d(TAG," updatePlayStatus " + mRemoteData.mMetadata.toString());
+ values.put(BluetoothAvrcpInfo.PLAY_STATUS,
+ getPlayStatusString(mRemoteData.mMetadata.playStatus));
+ values.put(BluetoothAvrcpInfo.PLAYING_TIME, mRemoteData.mMetadata.playTime);
+ values.put(BluetoothAvrcpInfo.TOTAL_TRACK_TIME, mRemoteData.mMetadata.totalTrackLen);
+ int rowsUpdated = sAvrcpControllerService.getContentResolver().
+ update(BluetoothAvrcpInfo.CONTENT_URI,values, null, null);
+ Log.d(TAG," updatePlayStatus num_rows_updated " + rowsUpdated);
+ }
+ private boolean isEventSupported(byte eventId)
+ {
+ if ((eventId == EVENT_PLAYBACK_STATUS_CHANGED)||
+ (eventId == EVENT_PLAYBACK_POS_CHANGED)||
+ (eventId == EVENT_PLAYER_APPLICATION_SETTINGS_CHANGED)||
+ (eventId == EVENT_TRACK_CHANGED))
+ return true;
+ else
+ return false;
+ }
+ private void handleNotificationTimeout(int cmd)
+ {
+ Log.d(TAG," handleNotificationTimeout cmd " + cmd);
+ int notificaitonId = cmd - 2000;
+ int index;
+ for (index = 0; index < mRemoteData.mNotifyEvent.size(); index++) {
+ if (notificaitonId == mRemoteData.mNotifyEvent.get(index).notify_event_id)
+ break;
+ }
+ if (index == mRemoteData.mNotifyEvent.size())
+ return;
+ NotifyEvents notifyEvent = mRemoteData.mNotifyEvent.get(index);
+ if ((notifyEvent.notify_event_id == notificaitonId) &&
+ (notifyEvent.notify_state == NOTIFY_INTERIM_EXPECTED))
+ {
+ notifyEvent.notify_state = NOTIFY_NOT_NOTIFIED;
+ registerFurtherNotification(notificaitonId);
+ }
+ }
+ private void handleCmdTimeout(int cmd)
+ {
+ Log.d(TAG," CMD " + cmd + " Timeout Happened");
+ switch(cmd)
+ {
+ case MESSAGE_GET_SUPPORTED_COMPANY_ID:
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ break;
+ case MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB:
+ case MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ mHandler.sendEmptyMessage(MESSAGE_GET_SUPPORTED_EVENTS);
+ break;
+ case MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES:
+ getFurtherPlayerSettingAttrib(ATTRIBUTE_FETCH_SKIP);
+ break;
+ case MESSAGE_GET_SUPPORTED_EVENTS:
+ /*
+ * If Timeout for these command happened, We would not do anything
+ * We will wait for Application to send request for
+ * ElementAttributes/PlayStatus. Only after that MetaData/Playstatus will resume
+ */
+ break;
+ case MESSAGE_TIMEOUT_APPL_SETTINGS_CHANGED:
+ case MESSAGE_TIMEOUT_PLAYBACK_POS_CHNAGED:
+ case MESSAGE_TIMEOUT_PLAYBACK_STATUS_CHANGED:
+ case MESSAGE_TIMEOUT_TRACK_CHANGED:
+ case MESSAGE_TIMEOUT_VOLUME_CHANGED:
+ handleNotificationTimeout(cmd);
+ break;
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TITLE:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_ARTIST_NAME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_ALBUM_NAME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_GENRE:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_PLAYING_TIME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TRACK_NUMBER:
+ getFurtherElementAttribute(ATTRIBUTE_FETCH_SKIP);
+ break;
+ case MESSAGE_GET_PLAY_STATUS:
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAY_STATUS); // reque this command
+ break;
+ case MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ // do nothing here
+ break;
+ }
+ }
+ 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 void resetElementAttribute(int attributeId, int asciiStringLen) {
+ if(asciiStringLen == 0) {
+ switch(attributeId)
+ {
+ case MEDIA_ATTRIBUTE_TITLE:
+ mRemoteData.mMetadata.trackTitle = BluetoothAvrcpInfo.TITLE_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_ARTIST_NAME:
+ mRemoteData.mMetadata.artist = BluetoothAvrcpInfo.ARTIST_NAME_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_ALBUM_NAME:
+ mRemoteData.mMetadata.albumTitle = BluetoothAvrcpInfo.ALBUM_NAME_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_GENRE:
+ mRemoteData.mMetadata.genre = BluetoothAvrcpInfo.GENRE_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ mRemoteData.mMetadata.trackNum = BluetoothAvrcpInfo.TRACK_NUM_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ mRemoteData.mMetadata.totalTrackNum = BluetoothAvrcpInfo.TOTAL_TRACKS_INVALID;
+ break;
+ case MEDIA_ATTRIBUTE_PLAYING_TIME:
+ mRemoteData.mMetadata.totalTrackLen = BluetoothAvrcpInfo.TOTAL_TRACK_TIME_INVALID;
+ break;
+ }
+ }
+ }
+ private int parseElementAttributes(int currentIndex, int attributeId, ByteBuffer attribBuffer)
+ {
+ Log.d(TAG,"parseElementAttributes Id = " + attributeId);
+ currentIndex += 2; // for character set
+ int asciiStringLen = attribBuffer.getChar(currentIndex);
+ Log.d(TAG," asciiStringLen "+ asciiStringLen);
+ currentIndex += 2;// for string len
+ if((asciiStringLen <= 0) || ((currentIndex + asciiStringLen) > attribBuffer.capacity()))
+ {
+ Log.d(TAG," parseElementAttribute wrong buffer");
+ resetElementAttribute(attributeId, asciiStringLen);
+ return currentIndex;
+ }
+ byte[] asciiString = new byte[asciiStringLen];
+ attribBuffer.position(currentIndex);
+ attribBuffer.get(asciiString, 0, asciiStringLen);
+ currentIndex += asciiStringLen;
+ switch(attributeId)
+ {
+ case MEDIA_ATTRIBUTE_TITLE:
+ mRemoteData.mMetadata.trackTitle = utf8ToString(asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_ARTIST_NAME:
+ mRemoteData.mMetadata.artist = utf8ToString(asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_ALBUM_NAME:
+ mRemoteData.mMetadata.albumTitle = utf8ToString(asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_GENRE:
+ mRemoteData.mMetadata.genre = utf8ToString(asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ mRemoteData.mMetadata.trackNum = asciiToInt(asciiStringLen,asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ mRemoteData.mMetadata.totalTrackNum = asciiToInt(asciiStringLen,asciiString);
+ break;
+ case MEDIA_ATTRIBUTE_PLAYING_TIME:
+ mRemoteData.mMetadata.totalTrackLen = asciiToInt(asciiStringLen,asciiString);
+ break;
+ }
+ return currentIndex;
+ }
+ private void handleProcessGetElementAttribute(int numAttributes, ByteBuffer attribBuffer)
+ {
+ Log.d(TAG,"handleProcessGetElementAttribute numAttrib ="+ numAttributes);
+ int attributeId = 0;
+ int currentIndex = 0;
+ for (int count = 0; count < numAttributes; count++)
+ {
+ /*
+ * In case remote sends rsp for more attributes that we can accomodate.
+ * Basically to avoid erroneous conditions.
+ */
+ if (mRemoteData.mMetadata.attributesFetchedId >= requestedElementAttribs.length)
+ continue;
+ attributeId = attribBuffer.getInt(currentIndex);
+ if (requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId] != attributeId)
+ {
+ Log.e(TAG," Received Rsp for attributeId "+ attributeId +" Requested ID = " +
+ requestedElementAttribs[mRemoteData.mMetadata.attributesFetchedId]);
+ break;
+ }
+ currentIndex += 4; // 4 bytes for Int
+ Log.d(TAG," attributeID = "+ attributeId);
+ /*
+ * remove timeout message if it is present already in que.
+ */
+ if (mHandler.hasMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + attributeId))
+ {
+ mHandler.removeMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + attributeId);
+ Log.d(TAG," Timeout CMD = " + attributeId + "dequed");
+ }
+ currentIndex = parseElementAttributes(currentIndex,attributeId,attribBuffer);
+ }
+ getFurtherElementAttribute(ATTRIBUTE_FETCH_CONTINUE);
+ }
+ private void handleProcessNotificationResponse(int notificationId, int notificationType,
+ ByteBuffer notificationRsp)
+ {
+ Log.d(TAG,"handleProcessNotificationResponse id " + notificationId +
+ " type = " + notificationType);
+ /*
+ * First remove timeout if already queued
+ */
+ byte oldState = NOTIFY_NOT_NOTIFIED;
+ if (mHandler.hasMessages(2000 + notificationId))
+ {
+ mHandler.removeMessages(2000 + notificationId);
+ Log.d(TAG," Timeout Notification CMD dequeued ");
+ }
+ for (NotifyEvents notifyEvent: mRemoteData.mNotifyEvent)
+ {
+ if (notificationId != notifyEvent.notify_event_id)
+ continue;
+ oldState = notifyEvent.notify_state;
+ if ((oldState == NOTIFY_INTERIM_EXPECTED) &&
+ (notificationType == NOTIFICATION_RSP_TYPE_INTERIM)) {
+ notifyEvent.notify_state = NOTIFY_CHANGED_EXPECTED;
+ }
+ else if ((oldState == NOTIFY_CHANGED_EXPECTED) &&
+ (notificationType == NOTIFICATION_RSP_TYPE_CHANGED)) {
+ notifyEvent.notify_state = NOTIFY_NOT_NOTIFIED;
+ }
+ break;
+ }
+ switch(notificationId)
+ {
+ case EVENT_PLAYBACK_STATUS_CHANGED:
+ byte oldPlayStatus = mRemoteData.mMetadata.playStatus;
+ mRemoteData.mMetadata.playStatus = notificationRsp.get(1);
+ /*
+ * Check if there is a transition from Stopped/Paused to Playing
+ * and Remote does not support EVENT_PLAYBACK_POS
+ * We need to Que GetPlayBackStatus command.
+ */
+ if (((oldPlayStatus == PLAY_STATUS_STOPPED)||
+ (oldPlayStatus == PLAY_STATUS_PAUSED))&&
+ (mRemoteData.mMetadata.playStatus == PLAY_STATUS_PLAYING)) {
+ if (!(mRemoteData.mEventsSupported.contains(EVENT_PLAYBACK_POS_CHANGED))||
+ !(mRemoteData.mEventsSupported.contains(EVENT_PLAYBACK_STATUS_CHANGED))) {
+ Log.d(TAG," State Transition Triggered, Que GetPlayStatus ");
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAY_STATUS);
+ }
+ }
+ updatePlayStatus();
+ break;
+ case EVENT_PLAYBACK_POS_CHANGED:
+ mRemoteData.mMetadata.playTime = notificationRsp.getInt(1);
+ updatePlayTime();
+ break;
+ case EVENT_PLAYER_APPLICATION_SETTINGS_CHANGED:
+ int numPlayerSettingAttribs = notificationRsp.get(1);
+ int attribIndex = 2; // first attribute will be at 2 index
+ for (int count = 0; count < numPlayerSettingAttribs; count ++)
+ {
+ Byte attributeId = notificationRsp.get(attribIndex);
+ for (PlayerSettings plSettings:
+ mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSettings.attr_Id == attributeId)
+ {
+ plSettings.attr_val = notificationRsp.get(attribIndex+1);
+ }
+ }
+ attribIndex = attribIndex + 2;
+ }
+ updatePlayerApplicationSettings();
+ if ((oldState == NOTIFY_CHANGED_EXPECTED) &&
+ (notificationType == NOTIFICATION_RSP_TYPE_CHANGED))
+ {
+ mHandler.sendEmptyMessage(MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ }
+ break;
+ case EVENT_TRACK_CHANGED:
+ if ((oldState == NOTIFY_CHANGED_EXPECTED) &&
+ (notificationType == NOTIFICATION_RSP_TYPE_CHANGED))
+ {
+ Log.d(TAG," Track change Happened, que GetElement, PlayerSetting ");
+ mHandler.sendEmptyMessage(MESSAGE_GET_ELEMENT_ATTRIBUTE);
+ mHandler.sendEmptyMessage(MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ }
+ break;
+ }
+ registerFurtherNotification(EVENT_NOTIFICAION_ID_NONE);
+ }
+ private void handleProcessPlayStatus(ByteBuffer bb)
+ {
+ int currentIndex = 0;
+ int bufSize = bb.capacity();
+ mRemoteData.mMetadata.totalTrackLen = bb.getInt(currentIndex);
+ currentIndex += 4;
+ mRemoteData.mMetadata.playTime = bb.getInt(currentIndex);
+ currentIndex += 4;
+ mRemoteData.mMetadata.playStatus = bb.get(currentIndex);
+ updatePlayStatus();
+ if (!(mRemoteData.mEventsSupported.contains(EVENT_PLAYBACK_POS_CHANGED))||
+ !(mRemoteData.mEventsSupported.contains(EVENT_PLAYBACK_STATUS_CHANGED)))
+ {
+ if ((mRemoteData.mMetadata.playStatus != PLAY_STATUS_STOPPED)&&
+ (mRemoteData.mMetadata.playStatus != PLAY_STATUS_PAUSED)) {
+ Log.d(TAG," POS and Status changed not supported");
+ mHandler.sendEmptyMessageDelayed(MESSAGE_GET_PLAY_STATUS, GET_PLAY_STATUS_INTERVAL);
+ }
+ }
+ }
+ private void setAbsVolume(int absVol)
+ {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int newIndex = (maxVolume*absVol)/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) {
+ if (mRemoteData.absVolNotificationState == NOTIFY_RSP_INTERIM_SENT)
+ mRemoteData.absVolNotificationState = NOTIFY_RSP_ABS_VOL_DEFERRED;
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, 0);
+ }
+ sendAbsVolRspNative(absVol);
+ }
+ private void handleProcessAbsVolNotification()
+ {
+ Log.d(TAG," handleProcessAbsVolNotification ");
+ if(mRemoteData == null)
+ return;
+ if(mRemoteData.absVolNotificationState == NOTIFY_NOT_REGISTERED)
+ {
+ int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex*ABS_VOL_BASE)/maxVol);
+ Log.d(TAG," maxVol ="+maxVol+" currentIndex ="+currIndex+
+ " percentageVol ="+percentageVol);
+ byte rspType = NOTIFICATION_RSP_TYPE_INTERIM;
+ mRemoteData.absVolNotificationState = NOTIFY_RSP_INTERIM_SENT;
+ sendRegisterAbsVolRspNative(rspType,percentageVol);
+ }
+ }
/** Handles Avrcp messages. */
private final class AvrcpMessageHandler extends Handler {
private AvrcpMessageHandler(Looper looper) {
@@ -224,12 +1630,199 @@ public class AvrcpControllerService extends ProfileService {
@Override
public void handleMessage(Message msg) {
+ Log.d(TAG," HandleMessage: "+ 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");
BluetoothDevice device = (BluetoothDevice)msg.obj;
sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
break;
+ case MESSAGE_GET_SUPPORTED_COMPANY_ID: //first command in AVRCP 1.3 that we send.
+ /*
+ * If Sink is not active we won't allow AVRCP MetaData
+ */
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if (a2dpSinkService == null) {
+ Log.e(TAG," A2DP Sink service not started, MetaData will not proceed");
+ return;
+ }
+ initializeDatabase();
+ getSupportedCapabilities(COMPANY_ID);
+ break;
+ case MESSAGE_GET_SUPPORTED_EVENTS:
+ if ((mRemoteData.mNotifyEvent != null)&&(mRemoteData.mNotifyEvent.isEmpty()))
+ getSupportedCapabilities(EVENTS_SUPPORTED);
+ break;
+ case MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB:
+ getPlayerApplicationSettingsAttrib();
+ break;
+ case MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES:
+ getFurtherPlayerSettingAttrib(ATTRIBUTE_FETCH_FRESH);
+ break;
+ case MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ if (!(mHandler.hasMessages(MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS)))
+ getCurrentPlayerApplicationSettingsValues();
+ break;
+ case MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ setCurrentPlayerApplicationSettingsValues(msg.arg1, msg.arg2);
+ break;
+ case MESSAGE_DEINIT_AVRCP_DATABASE:
+ deinitDatabase();
+ break;
+ case MESSAGE_CMD_TIMEOUT:
+ int cmd = (Integer)msg.obj;
+ handleCmdTimeout(cmd);
+ break;
+ case MESSAGE_TIMEOUT_APPL_SETTINGS_CHANGED:
+ case MESSAGE_TIMEOUT_PLAYBACK_POS_CHNAGED:
+ case MESSAGE_TIMEOUT_PLAYBACK_STATUS_CHANGED:
+ case MESSAGE_TIMEOUT_TRACK_CHANGED:
+ case MESSAGE_TIMEOUT_VOLUME_CHANGED:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TITLE:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_ARTIST_NAME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_ALBUM_NAME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_GENRE:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_PLAYING_TIME:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ case MESSAGE_TIMEOUT_ATTRIBUTE_TRACK_NUMBER:
+ handleCmdTimeout(msg.what);
+ break;
+
+ case MESSAGE_GET_ELEMENT_ATTRIBUTE:
+ getFurtherElementAttribute(ATTRIBUTE_FETCH_FRESH);
+ break;
+ case ABORT_FETCH_ELEMENT_ATTRIBUTE:
+ if ((mRemoteData != null) && (mRemoteData.mMetadata != null))
+ mRemoteData.mMetadata.attributesFetchedId = -1; // reset it to -1 again.
+ break;
+ case MESSAGE_GET_PLAY_STATUS:
+ if (mHandler.hasMessages(MESSAGE_GET_PLAY_STATUS)) {
+ Log.d(TAG," Get Play Status Already There, return ");
+ return;
+ }
+ Message messsag = mHandler.obtainMessage(MESSAGE_CMD_TIMEOUT,
+ 0,0,MESSAGE_GET_PLAY_STATUS);
+ mHandler.sendMessageDelayed(messsag, MSG_TIMEOUT_MTP);
+ getPlayStatusNative();
+ break;
+ case MESSAGE_PROCESS_RC_FEATURES:
+ /* check if we have already initiated MetaData Procedure */
+ if ((mRemoteData != null)&&(mRemoteData.mRemoteFeatures != 0))
+ break;
+ /* this is first time */
+ int remoteFeatures = msg.arg1;
+ BluetoothDevice remoteDevice = (BluetoothDevice)msg.obj;
+ if (mConnectedDevices.contains(remoteDevice)) {
+ mRemoteData.mRemoteFeatures = remoteFeatures;
+ if ((mRemoteData.mRemoteFeatures & BTRC_FEAT_METADATA) != 0)
+ mHandler.sendMessage(mHandler.
+ obtainMessage(MESSAGE_GET_SUPPORTED_COMPANY_ID,0, 0, remoteDevice));
+ }
+ break;
+ case MESSAGE_PROCESS_CONNECTION_CHANGE:
+ int connected = msg.arg1;
+ BluetoothDevice rtDevice = (BluetoothDevice)msg.obj;
+ if (connected == 1)
+ {
+ /*
+ * Connection up but RC features not received yet. We will
+ * send get_company_ID later.
+ */
+ if (mRemoteData == null)
+ mRemoteData = new RemoteAvrcpData();
+ mRemoteData.mCompanyIDSupported = new ArrayList<Integer>();
+ mRemoteData.mEventsSupported = new ArrayList<Byte>();
+ mRemoteData.mMetadata = new Metadata();
+ mRemoteData.mNotifyEvent = new ArrayList<NotifyEvents>();
+ mRemoteData.mSupportedApplicationSettingsAttribute =
+ new ArrayList<PlayerSettings>();
+ mRemoteData.absVolNotificationState = NOTIFY_NOT_REGISTERED;
+ mRemoteData.playerSettingAttribIdFetch = 0;
+ mRemoteData.mRemoteFeatures = 0;
+ }
+ else
+ {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.sendEmptyMessage(MESSAGE_DEINIT_AVRCP_DATABASE);
+ if (mRemoteData != null)
+ {
+ mRemoteData.mCompanyIDSupported.clear();
+ mRemoteData.mEventsSupported.clear();
+ mRemoteData.mMetadata.resetMetaData();
+ mRemoteData.mNotifyEvent.clear();
+ mRemoteData.mSupportedApplicationSettingsAttribute.clear();
+ mRemoteData.absVolNotificationState = NOTIFY_NOT_REGISTERED;
+ mRemoteData.mRemoteFeatures = 0;
+ Log.d(TAG," RC_features, conn_change down " + mRemoteData.mRemoteFeatures);
+ mRemoteData.playerSettingAttribIdFetch = 0;
+ mRemoteData = null;
+ }
+ }
+ break;
+ case MESSAGE_PROCESS_SUPPORTED_COMPANY_ID:
+ List<Integer> company_ids = (List<Integer>)msg.obj;
+ mRemoteData.mCompanyIDSupported.addAll(company_ids);
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ break;
+ case MESSAGE_PROCESS_SUPPORTED_EVENTS:
+ List<Byte> events_supported = (List<Byte>)msg.obj;
+ mRemoteData.mEventsSupported.addAll(events_supported);
+ for (Byte event: mRemoteData.mEventsSupported)
+ {
+ NotifyEvents notifyevent = new NotifyEvents();
+ notifyevent.notify_event_id = event;
+ notifyevent.notify_state = NOTIFY_NOT_NOTIFIED;
+ mRemoteData.mNotifyEvent.add(notifyevent);
+ }
+ registerFurtherNotification(EVENT_NOTIFICAION_ID_NONE);
+ break;
+ case MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_ATTRIB:
+ List<PlayerSettings> player_settings_supported = (List<PlayerSettings>)msg.obj;
+ mRemoteData.mSupportedApplicationSettingsAttribute.
+ addAll(player_settings_supported);
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES);
+ break;
+ case MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_VALUES:
+ mRemoteData.playerSettingAttribIdFetch ++;
+ getFurtherPlayerSettingAttrib(msg.arg1);
+ break;
+ case MESSAGE_PROCESS_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ updatePlayerApplicationSettings();
+ if ((mRemoteData.mNotifyEvent != null)&&(mRemoteData.mNotifyEvent.isEmpty()))
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_GET_SUPPORTED_EVENTS));
+ break;
+ case MESSAGE_PROCESS_NOTIFICATION_RESPONSE:
+ int notificationId = msg.arg1;
+ int notificationType = msg.arg2;
+ ByteBuffer bb = (ByteBuffer)msg.obj;
+ handleProcessNotificationResponse(notificationId, notificationType, bb);
+ break;
+ case MESSAGE_PROCESS_ELEMENT_ATTRIBUTE:
+ if ((mRemoteData.mMetadata.attributesFetchedId == -1) ||
+ (mHandler.hasMessages(ABORT_FETCH_ELEMENT_ATTRIBUTE))) {
+ Log.d(TAG," ID reset, Fetch from Fresh");
+ mHandler.sendEmptyMessage(MESSAGE_GET_ELEMENT_ATTRIBUTE);
+ break;
+ }
+ int processMode = msg.arg2;
+ if (processMode == ATTRIBUTE_FETCH_CONTINUE)
+ {
+ int numAttributes = msg.arg1;
+ ByteBuffer bbRsp = (ByteBuffer)msg.obj;
+ handleProcessGetElementAttribute(numAttributes, bbRsp);
+ }
+ else if(processMode == ATTRIBUTE_FETCH_SKIP)
+ getFurtherElementAttribute(ATTRIBUTE_FETCH_SKIP);
+ break;
+ case MESSAGE_PROCESS_PLAY_STATUS:
+ ByteBuffer playStatusRsp = (ByteBuffer)msg.obj;
+ handleProcessPlayStatus(playStatusRsp);
+ break;
+ case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ setAbsVolume(msg.arg1);
+ break;
+ case MESSAGE_PROCESS_REGISTER_ABS_VOL_REQUEST:
+ handleProcessAbsVolNotification();
}
}
}
@@ -237,6 +1830,8 @@ public class AvrcpControllerService extends ProfileService {
private void onConnectionStateChanged(boolean connected, byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
(Utils.getAddressStringFromByte(address));
+ if (device == null)
+ return;
Log.d(TAG, "onConnectionStateChanged " + connected + " " + device);
Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
@@ -248,18 +1843,121 @@ public class AvrcpControllerService extends ProfileService {
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) {
mConnectedDevices.add(device);
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_CONNECTION_CHANGE, 1, 0, device);
+ mHandler.sendMessage(msg);
} else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
mConnectedDevices.remove(device);
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_CONNECTION_CHANGE, 0, 0, device);
+ mHandler.sendMessage(msg);
}
}
+ private void getRcFeatures(byte[] address, int features) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_RC_FEATURES, features, 0,
+ device);
+ mHandler.sendMessage(msg);
+ }
+
private void handlePassthroughRsp(int id, int keyState) {
Log.d(TAG, "passthrough response received as: key: "
+ id + " state: " + keyState);
}
+ private void handleGetCapabilitiesResponse(byte[] address, int capability_id,
+ int[] supported_values,int num_supported, byte rsp_type)
+ {
+ Log.d(TAG, "handleGetCapabilitiesResponse cap_id" + capability_id + " num_supported "
+ + num_supported+ "rsp_type " + rsp_type);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (num_supported <= 0) {
+ /*
+ * supported values are not reliable. Drop further processing
+ * Remove Timeout Commands
+ */
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,COMPANY_ID))
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT, COMPANY_ID);
+ else if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,EVENTS_SUPPORTED))
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT, EVENTS_SUPPORTED);
+ return;
+ }
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT, capability_id))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT, capability_id);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ if (rsp_type != AVRC_RSP_IMPL_STBL)
+ {
+ if(capability_id == COMPANY_ID)
+ mHandler.sendEmptyMessage(MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ /* for events supported failure, we don't do anything */
+ return;
+ }
+ Message msg;
+ switch(capability_id)
+ {
+ case COMPANY_ID:
+ ArrayList<Integer> supportedCompanyIds = new ArrayList<Integer>();
+ for (int count = 0; count<num_supported; ++count)
+ supportedCompanyIds.add(supported_values[count]);
+ msg = mHandler.
+ obtainMessage(MESSAGE_PROCESS_SUPPORTED_COMPANY_ID,supportedCompanyIds);
+ mHandler.sendMessage(msg);
+ break;
+ case EVENTS_SUPPORTED:
+ ArrayList<Byte> supportedEvents = new ArrayList<Byte>();
+ for (int count = 0; count<num_supported; ++count)
+ {
+ Byte supported_event =
+ Byte.valueOf((Integer.valueOf(supported_values[count])).byteValue());
+ if (!isEventSupported(supported_event))
+ continue;
+ supportedEvents.add(supported_event);
+ }
+ msg = mHandler.obtainMessage(MESSAGE_PROCESS_SUPPORTED_EVENTS,supportedEvents);
+ mHandler.sendMessage(msg);
+ break;
+ }
+ }
+ private void handleListPlayerApplicationSettingsAttrib(byte[] address,
+ byte[] supported_setting_attrib,int num_attrib, byte rsp_type)
+ {
+ Log.d(TAG, "handleListPlayerApplicationSettingsAttrib "
+ + num_attrib + " rsp_type " + rsp_type);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_ATTRIB);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ if ((rsp_type != AVRC_RSP_IMPL_STBL)||(num_attrib <= 0))
+ {
+ mHandler.sendEmptyMessage(MESSAGE_GET_SUPPORTED_EVENTS);
+ return;
+ }
+ ArrayList<PlayerSettings> supported_attrib = new ArrayList<PlayerSettings>();
+ for (int count = 0; count < num_attrib; count++)
+ {
+ PlayerSettings attrib = new PlayerSettings();
+ attrib.attr_Id = supported_setting_attrib[count];
+ attrib.supported_values = null;
+ supported_attrib.add(attrib);
+ }
+ mHandler.sendMessage(mHandler.
+ obtainMessage(MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_ATTRIB,supported_attrib));
+ }
private byte[] getByteAddress(BluetoothDevice device) {
return Utils.getBytesFromAddress(device.getAddress());
}
@@ -269,8 +1967,207 @@ public class AvrcpControllerService extends ProfileService {
super.dump(sb);
}
+ private void handleListPlayerApplicationSettingValue(byte[] address, byte[] supported_val,
+ byte num_supported_val, byte rsp_type)
+ {
+ Log.d(TAG,"handleListPlayerApplicationSettingValue num_supported " + num_supported_val
+ + " rsp_type " + rsp_type);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_PLAYER_APPLICATION_SETTINGS_VALUES);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ if ((rsp_type != AVRC_RSP_IMPL_STBL)||(num_supported_val <= 0))
+ {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_VALUES, ATTRIBUTE_FETCH_SKIP, 0));
+ return;
+ }
+ int fetch_id = mRemoteData.playerSettingAttribIdFetch;
+ PlayerSettings plSetting = mRemoteData.mSupportedApplicationSettingsAttribute.get(fetch_id);
+ plSetting.supported_values = new byte [num_supported_val];
+ for (int count = 0; count < num_supported_val; ++count)
+ plSetting.supported_values[count] = supported_val[count];
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_PROCESS_PLAYER_APPLICATION_SETTINGS_VALUES, ATTRIBUTE_FETCH_CONTINUE, 0));
+ }
+ private void handleCurrentPlayerApplicationSettingsResponse(byte[] address,
+ byte[] ids, byte[] values,byte num_attrib, byte rsp_type)
+ {
+ Log.d(TAG,"handleCurrentPlayerApplicationSettingsResponse num_attrib =" + num_attrib);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ if ((rsp_type != AVRC_RSP_IMPL_STBL)||(num_attrib <= 0))
+ {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_GET_SUPPORTED_EVENTS));
+ return;
+ }
+ for (int count = 0; count < num_attrib; count++)
+ {
+ byte attribute = ids[count]; // count increment to point to value of attrib_id.
+ for (PlayerSettings plSetting: mRemoteData.mSupportedApplicationSettingsAttribute)
+ {
+ if (plSetting.attr_Id == attribute) {
+ plSetting.attr_val = values[count];
+ }
+ }
+ }
+ mHandler.sendEmptyMessage(MESSAGE_PROCESS_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ }
+ /*
+ * response contains array from Event-ID ( first byte of the array ).
+ * rspLen is same as Parameter Length field of PDU.
+ * rspType - either Interim or Notify.
+ */
+ private void handleNotificationRsp(byte[] address, byte rspType, int rspLen, byte[] response)
+ {
+ Log.d(TAG,"handleNotificationRsp ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if ((!mConnectedDevices.contains(device))||(rspLen <= 0))
+ return;
+ int notificationEventId = response[0];
+ int notificaionRspType = rspType;
+ Log.d(TAG," rsp_type " + rspType +" notificationId " + notificationEventId);
+ if ((rspType != AVRC_RSP_INTERIM)&&(rspType != AVRC_RSP_CHANGED))
+ {
+ if (mHandler.hasMessages(2000 + notificationEventId))
+ {
+ mHandler.removeMessages(2000 + notificationEventId);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ handleNotificationTimeout(2000 + notificationEventId);
+ return;
+ }
+ ByteBuffer bb = ByteBuffer.wrap(response, 0, rspLen);
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_PROCESS_NOTIFICATION_RESPONSE,
+ notificationEventId, notificaionRspType, bb));
+ }
+ /*
+ * attribRsp contains array after Number of Attributes
+ * First byte would be attribute ID ( 4 octets )of first attribute.
+ * attribRspLen - total length of attribRsp
+ */
+ private void handleGetElementAttributes(byte[] address, byte numAttributes, int attribRspLen,
+ byte[] attribRsp, byte rsp_type)
+ {
+ Log.d(TAG,"handleGetElementAttributes ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if ((!mConnectedDevices.contains(device))||(attribRspLen <= 0))
+ return;
+
+ ByteBuffer bb = ByteBuffer.wrap(attribRsp, 0, attribRspLen);
+ int attributeId = bb.getInt(0);
+ Log.d(TAG," numAttrib " + numAttributes + " attribRspLen " + attribRspLen +
+ " rsp_type " + rsp_type + " attribId " + attributeId);
+ if (rsp_type != AVRC_RSP_IMPL_STBL)
+ {
+ if (mHandler.hasMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + attributeId))
+ {
+ mHandler.removeMessages(GET_ELEMENT_ATTR_TIMEOUT_BASE + attributeId);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_PROCESS_ELEMENT_ATTRIBUTE,
+ 0, ATTRIBUTE_FETCH_SKIP));
+ return;
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_ELEMENT_ATTRIBUTE, numAttributes,
+ ATTRIBUTE_FETCH_CONTINUE, bb);
+ mHandler.sendMessage(msg);
+ }
+ /*
+ * playStatusRsp will be after Parameter Length Feild
+ * first byte will be from Total Song Length
+ */
+ private void handleGetPlayStatus(byte[] address, int paramLen,
+ byte[] playStatusRsp, byte rsp_type)
+ {
+ Log.d(TAG,"handleGetPlayStatus ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT, MESSAGE_GET_PLAY_STATUS))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT, MESSAGE_GET_PLAY_STATUS);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ if ((rsp_type!= AVRC_RSP_IMPL_STBL)||(paramLen <= 0)) {
+ return;
+ }
+ ByteBuffer bb = ByteBuffer.wrap(playStatusRsp, 0, paramLen);
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_PLAY_STATUS, 0, 0, bb);
+ mHandler.sendMessage(msg);
+ }
+ private void handleSetAbsVolume(byte[] address, byte absVol)
+ {
+ Log.d(TAG,"handleSetAbsVolume ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ Message msg = mHandler.obtainMessage(MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, 0);
+ mHandler.sendMessage(msg);
+ }
+ private void handleRegisterNotificationAbsVol(byte[] address)
+ {
+ Log.d(TAG,"handleRegisterNotificationAbsVol ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ mHandler.sendEmptyMessage(MESSAGE_PROCESS_REGISTER_ABS_VOL_REQUEST);
+ }
+ private void handleSetPlayerApplicationResponse(byte[] address, byte rsp_type)
+ {
+ Log.d(TAG,"handleSetPlayerApplicationResponse rsp_type = "+ rsp_type);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ if (mHandler.hasMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS))
+ {
+ mHandler.removeMessages(MESSAGE_CMD_TIMEOUT,
+ MESSAGE_SET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ Log.d(TAG," Timeout CMD dequeued ");
+ }
+ /*
+ * Send GetPlayerAppSetting if we do not support EVENT_PLAYER_APPLICATION_SETTINGS_CHANGED
+ * Some Devices don't reply with Changed. So we have to send this command, though its a
+ * overhead for other devices.
+ */
+ mHandler.sendEmptyMessage(MESSAGE_GET_CURRENT_PLAYER_APPLICATION_SETTINGS);
+ }
private native static void classInitNative();
private native void initNative();
private native void cleanupNative();
private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+ private native void getCapabilitiesNative(int capId);
+ private native void listPlayerApplicationSettingAttributeNative();
+ private native void listPlayerApplicationSettingValueNative(byte attribId);
+ private native void getPlayerApplicationSettingValuesNative(byte numAttrib, byte[] attribIds);
+ private native void setPlayerApplicationSettingValuesNative(byte numAttrib,
+ byte[] atttibIds, byte[]attribVal);
+ private native void registerNotificationNative(byte eventId, int value);
+ private native void getElementAttributeNative(byte numAttributes, int attribId);
+ private native void getPlayStatusNative();
+ private native void sendAbsVolRspNative(int absVol);
+ private native void sendRegisterAbsVolRspNative(byte rspType, int absVol);
}
diff --git a/src/com/android/bluetooth/avrcp/BluetoothAvrcpDataProvider.java b/src/com/android/bluetooth/avrcp/BluetoothAvrcpDataProvider.java
new file mode 100644
index 000000000..7bee7c1e9
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/BluetoothAvrcpDataProvider.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Motorola, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.content.UriMatcher;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.util.Log;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * This content-provider implements AVRCP-MetaData database.
+ */
+
+public final class BluetoothAvrcpDataProvider extends ContentProvider {
+
+ private static final String TAG = "BluetoothAvrcpDataProvider";
+ private static final String DB_NAME = "btavrcp_ct.db";
+ private static final int DB_VERSION = 1;
+ private static final int DB_VERSION_NOP_UPGRADE_FROM = 0;
+ private static final int DB_VERSION_NOP_UPGRADE_TO = 1;
+ private static final String DB_TABLE = "btavrcp_ct";
+ private static final String TRACK_LIST_TYPE = "vnd.android.cursor.dir/vnd.android.btavrcp_ct";
+ private static final String TRACK_TYPE = "vnd.android.cursor.item/vnd.android.btavrcp_ct";
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ /** For all TrackList */
+ private static final int TRACKS = 1;
+ /** For a single Track ID */
+ private static final int TRACK_ID = 2;
+
+ static {
+ sURIMatcher.addURI("com.android.bluetooth.avrcp", "btavrcp_ct", TRACKS);
+ sURIMatcher.addURI("com.android.bluetooth.avrcp", "btavrcp_ct/#", TRACK_ID);
+ }
+ private SQLiteOpenHelper mOpenHelper = null;
+
+ private final class DbHelper extends SQLiteOpenHelper {
+
+ public DbHelper(final Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ Log.v(TAG, "populating new database");
+ createTable(db);
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) {
+ if (oldV == DB_VERSION_NOP_UPGRADE_FROM) {
+ if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op
+ return;
+ }
+ oldV = DB_VERSION_NOP_UPGRADE_TO;
+ }
+ Log.i(TAG, "Upgrading downloads database from version " + oldV + " to "
+ + newV + ", which will destroy all old data");
+ dropTable(db);
+ createTable(db);
+ }
+ }
+
+ private void createTable(SQLiteDatabase db) {
+ try {
+ db.execSQL("CREATE TABLE " + DB_TABLE + "("
+ + BluetoothAvrcpInfo._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + BluetoothAvrcpInfo.TRACK_NUM + " INTEGER, "
+ + BluetoothAvrcpInfo.TITLE + " TEXT, "
+ + BluetoothAvrcpInfo.ARTIST_NAME + " TEXT, "
+ + BluetoothAvrcpInfo.ALBUM_NAME + " TEXT, "
+ + BluetoothAvrcpInfo.TOTAL_TRACKS + " INTEGER, "
+ + BluetoothAvrcpInfo.GENRE + " TEXT, "
+ + BluetoothAvrcpInfo.PLAYING_TIME + " INTEGER, "
+ + BluetoothAvrcpInfo.TOTAL_TRACK_TIME + " INTEGER, "
+ + BluetoothAvrcpInfo.PLAY_STATUS + " TEXT, "
+ + BluetoothAvrcpInfo.REPEAT_STATUS + " TEXT, "
+ + BluetoothAvrcpInfo.SHUFFLE_STATUS + " TEXT, "
+ + BluetoothAvrcpInfo.SCAN_STATUS + " TEXT, "
+ + BluetoothAvrcpInfo.EQUALIZER_STATUS + " TEXT); ");
+ } catch (SQLException ex) {
+ Log.e(TAG, "couldn't create table in downloads database");
+ throw ex;
+ }
+ }
+
+ private void dropTable(SQLiteDatabase db) {
+ try {
+ db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
+ } catch (SQLException ex) {
+ Log.e(TAG, "Couldn't Drop table");
+ throw ex;
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case TRACKS: {
+ return TRACK_LIST_TYPE;
+ }
+ case TRACK_ID: {
+ return TRACK_TYPE;
+ }
+ default: {
+ Log.d(TAG, "getType called on unknown URI: " + uri);
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+ }
+
+ private static final void copyString(String key, ContentValues from, ContentValues to) {
+ String s = from.getAsString(key);
+ if (s != null) {
+ to.put(key, s);
+ }
+ }
+
+ private static final void copyInteger(String key, ContentValues from, ContentValues to) {
+ Integer i = from.getAsInteger(key);
+ if (i != null) {
+ to.put(key, i);
+ }
+ }
+
+ private static final void copyLong(String key, ContentValues from, ContentValues to) {
+ Long i = from.getAsLong(key);
+ if (i != null) {
+ to.put(key, i);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ if (sURIMatcher.match(uri) != TRACKS) {
+ Log.d(TAG, "calling insert on an unknown/invalid URI: " + uri);
+ throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
+ }
+
+ ContentValues filteredValues = new ContentValues();
+
+ copyLong(BluetoothAvrcpInfo.TRACK_NUM, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.TITLE, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.ARTIST_NAME, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.ALBUM_NAME, values, filteredValues);
+ copyLong(BluetoothAvrcpInfo.TOTAL_TRACKS, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.GENRE, values, filteredValues);
+ copyLong(BluetoothAvrcpInfo.PLAYING_TIME, values, filteredValues);
+ copyLong(BluetoothAvrcpInfo.TOTAL_TRACK_TIME, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.PLAY_STATUS, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.REPEAT_STATUS, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.SHUFFLE_STATUS, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.SCAN_STATUS, values, filteredValues);
+ copyString(BluetoothAvrcpInfo.EQUALIZER_STATUS, values, filteredValues);
+
+ long rowID = db.insert(DB_TABLE, null, filteredValues);
+
+ Uri ret = null;
+ Context context = getContext();
+
+ if (rowID != -1) {
+ ret = Uri.parse(BluetoothAvrcpInfo.CONTENT_URI + "/" + rowID);
+ context.getContentResolver().notifyChange(uri, null);
+ } else {
+ Log.d(TAG, "couldn't insert into database");
+ }
+
+ return ret;
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DbHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case TRACKS: {
+ qb.setTables(DB_TABLE);
+ break;
+ }
+ case TRACK_ID: {
+ qb.setTables(DB_TABLE);
+ qb.appendWhere(BluetoothAvrcpInfo._ID + "=");
+ qb.appendWhere(uri.getPathSegments().get(1));
+ break;
+ }
+ default: {
+ Log.d(TAG, "querying unknown URI: " + uri);
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ {
+ java.lang.StringBuilder sb = new java.lang.StringBuilder();
+ sb.append("starting query, database is ");
+ if (db != null) {
+ sb.append("not ");
+ }
+ sb.append("null; ");
+ if (projection == null) {
+ sb.append("projection is null; ");
+ } else if (projection.length == 0) {
+ sb.append("projection is empty; ");
+ } else {
+ for (int i = 0; i < projection.length; ++i) {
+ sb.append("projection[");
+ sb.append(i);
+ sb.append("] is ");
+ sb.append(projection[i]);
+ sb.append("; ");
+ }
+ }
+ sb.append("selection is ");
+ sb.append(selection);
+ sb.append("; ");
+ if (selectionArgs == null) {
+ sb.append("selectionArgs is null; ");
+ } else if (selectionArgs.length == 0) {
+ sb.append("selectionArgs is empty; ");
+ } else {
+ for (int i = 0; i < selectionArgs.length; ++i) {
+ sb.append("selectionArgs[");
+ sb.append(i);
+ sb.append("] is ");
+ sb.append(selectionArgs[i]);
+ sb.append("; ");
+ }
+ }
+ sb.append("sort is ");
+ sb.append(sortOrder);
+ sb.append(".");
+ Log.v(TAG, sb.toString());
+ }
+
+ Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ Log.v(TAG," Query Done ");
+ if (ret != null) {
+ ret.setNotificationUri(getContext().getContentResolver(), uri);
+ Log.v(TAG, "created cursor " + ret + " on behalf of ");
+ } else {
+ Log.d(TAG, "query failed in downloads database");
+ }
+
+ return ret;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ int count;
+ long rowId = 0;
+
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case TRACKS:
+ case TRACK_ID: {
+ String myWhere;
+ if (selection != null) {
+ if (match == TRACKS) {
+ myWhere = "( " + selection + " )";
+ } else {
+ myWhere = "( " + selection + " ) AND ";
+ }
+ } else {
+ myWhere = "";
+ }
+ if (match == TRACK_ID) {
+ String segment = uri.getPathSegments().get(1);
+ rowId = Long.parseLong(segment);
+ myWhere += " ( " + BluetoothAvrcpInfo._ID + " = " + rowId + " ) ";
+ }
+
+ if (values.size() > 0) {
+ count = db.update(DB_TABLE, values, myWhere, selectionArgs);
+ } else {
+ count = 0;
+ }
+ break;
+ }
+ default: {
+ Log.d(TAG, "updating unknown/invalid URI: " + uri);
+ throw new UnsupportedOperationException("Cannot update URI: " + uri);
+ }
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ return count;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count;
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case TRACKS:
+ case TRACK_ID: {
+ String myWhere;
+ if (selection != null) {
+ if (match == TRACKS) {
+ myWhere = "( " + selection + " )";
+ } else {
+ myWhere = "( " + selection + " ) AND ";
+ }
+ } else {
+ myWhere = "";
+ }
+ if (match == TRACK_ID) {
+ String segment = uri.getPathSegments().get(1);
+ long rowId = Long.parseLong(segment);
+ myWhere += " ( " + BluetoothAvrcpInfo._ID + " = " + rowId + " ) ";
+ }
+
+ count = db.delete(DB_TABLE, myWhere, selectionArgs);
+ break;
+ }
+ default: {
+ Log.d(TAG, "deleting unknown/invalid URI: " + uri);
+ throw new UnsupportedOperationException("Cannot delete URI: " + uri);
+ }
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return count;
+ }
+} \ No newline at end of file