Merge "OPP: Fix "0%" progress even after file transfer finished"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0c71e21..c3ee7e4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -316,7 +316,7 @@
</service>
<service
android:process="@string/process"
- android:name = ".avrcp.AvrcpControllerService"
+ android:name = ".avrcpcontroller.AvrcpControllerService"
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 c28b15a..457b771 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -37,12 +37,22 @@
static jmethodID method_handletrackchanged;
static jmethodID method_handleplaypositionchanged;
static jmethodID method_handleplaystatuschanged;
+static jmethodID method_handleGetFolderItemsRsp;
+static jmethodID method_handleGetPlayerItemsRsp;
static jmethodID method_handleGroupNavigationRsp;
+static jmethodID method_createFromNativeMediaItem;
+static jmethodID method_createFromNativeFolderItem;
+static jmethodID method_createFromNativePlayerItem;
+static jmethodID method_handleChangeFolderRsp;
+static jmethodID method_handleSetBrowsedPlayerRsp;
+static jclass class_MediaBrowser_MediaItem;
+static jclass class_AvrcpPlayer;
static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
-static jobject mCallbacksObj = NULL;
+static jobject sCallbacksObj = NULL;
static JNIEnv *sCallbackEnv = NULL;
+static JNIEnv *sEnv = NULL;
static bool checkCallbackThread() {
// Always fetch the latest callbackEnv from AdapterService.
@@ -56,7 +66,7 @@
return true;
}
-static void btavrcp_passthrough_response_callback(int id, int pressed, bt_bdaddr_t* bd_addr) {
+static void btavrcp_passthrough_response_callback(bt_bdaddr_t* bd_addr, int id, int pressed) {
jbyteArray addr;
ALOGI("%s", __func__);
@@ -75,7 +85,7 @@
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughRsp, (jint)id,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handlePassthroughRsp, (jint)id,
(jint)pressed,
addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
@@ -84,23 +94,24 @@
}
static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
- ALOGI("%s", __FUNCTION__);
+ ALOGV("%s", __FUNCTION__);
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
(jint)pressed);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
-static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr) {
+static void btavrcp_connection_state_callback(
+ bool rc_connect, bool br_connect, bt_bdaddr_t* bd_addr) {
jbyteArray addr;
- ALOGI("%s", __FUNCTION__);
- ALOGI("conn state: %d", state);
+ ALOGV("%s", __FUNCTION__);
+ ALOGI("%s conn state rc: %d br: %d", __FUNCTION__, rc_connect, br_connect);
if (!checkCallbackThread()) { \
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -115,8 +126,8 @@
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jboolean) state,
- addr);
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_onConnectionStateChanged,
+ (jboolean) rc_connect, (jboolean) br_connect, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
@@ -124,7 +135,7 @@
static void btavrcp_get_rcfeatures_callback(bt_bdaddr_t *bd_addr, int features) {
jbyteArray addr;
- ALOGI("%s", __FUNCTION__);
+ ALOGV("%s", __FUNCTION__);
if (!checkCallbackThread()) { \
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -139,7 +150,7 @@
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr, (jint)features);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
@@ -148,7 +159,7 @@
uint8_t accepted) {
jbyteArray addr;
- ALOGI("%s", __FUNCTION__);
+ ALOGV("%s", __FUNCTION__);
if (!checkCallbackThread()) { \
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -163,7 +174,7 @@
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
@@ -171,7 +182,7 @@
static void btavrcp_playerapplicationsetting_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
btrc_player_app_attr_t *app_attrs, uint8_t num_ext_attr,
btrc_player_app_ext_attr_t *ext_attrs) {
- ALOGI("%s", __FUNCTION__);
+ ALOGV("%s", __FUNCTION__);
jbyteArray addr;
jbyteArray playerattribs;
jint arraylen;
@@ -198,7 +209,7 @@
/*2 bytes for id and num */
arraylen += 2 + app_attrs[i].num_val;
}
- ALOGI(" arraylen %d", arraylen);
+ ALOGV(" arraylen %d", arraylen);
playerattribs = sCallbackEnv->NewByteArray(arraylen);
if(!playerattribs)
{
@@ -218,7 +229,7 @@
(jbyte*)(app_attrs[i].attr_val));
k = k + app_attrs[i].num_val;
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsetting, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplayerappsetting, addr,
playerattribs, (jint)arraylen);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
@@ -265,7 +276,7 @@
sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_values[i]));
k++;
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsettingchanged, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplayerappsettingchanged, addr,
playerattribs, (jint)arraylen);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
@@ -291,7 +302,7 @@
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
(jbyte)label);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
@@ -315,7 +326,7 @@
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
(jbyte)label);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
@@ -379,7 +390,7 @@
sCallbackEnv->DeleteLocalRef(str);
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handletrackchanged, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handletrackchanged, addr,
(jbyte)(num_attr), attribIds, stringArray);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
@@ -402,7 +413,7 @@
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaypositionchanged, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaypositionchanged, addr,
(jint)(song_len), (jint)song_pos);
sCallbackEnv->DeleteLocalRef(addr);
}
@@ -419,11 +430,196 @@
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaystatuschanged, addr,
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaystatuschanged, addr,
(jbyte)play_status);
sCallbackEnv->DeleteLocalRef(addr);
}
+static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr,
+ const btrc_folder_items_t *folder_items, uint8_t count) {
+ /* Folder items are list of items that can be either BTRC_ITEM_PLAYER
+ * BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java
+ * counterparts by calling the java constructor for each of the items.
+ */
+ ALOGV("%s count %d", __FUNCTION__, count);
+
+ if (!checkCallbackThread()) {
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ return;
+ }
+
+ // Inspect if the first element is a folder/item or player listing. They are
+ // always exclusive.
+ bool isPlayerListing = count > 0 && (folder_items[0].item_type == BTRC_ITEM_PLAYER);
+
+ // Initialize arrays for Folder OR Player listing.
+ jobjectArray playerItemArray = NULL;
+ jobjectArray folderItemArray = NULL;
+ if (isPlayerListing) {
+ playerItemArray = sCallbackEnv->NewObjectArray((jint) count, class_AvrcpPlayer, 0);
+ if (!playerItemArray) {
+ ALOGE("%s playerItemArray allocation failed.", __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ } else {
+ folderItemArray = sCallbackEnv->NewObjectArray(
+ (jint) count, class_MediaBrowser_MediaItem, 0);
+ if (!folderItemArray) {
+ ALOGE("%s folderItemArray is empty.", __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ }
+
+ for (int i = 0; i < count; i++) {
+ const btrc_folder_items_t *item = &(folder_items[i]);
+ ALOGV("%s item type %d", __FUNCTION__, item->item_type);
+ switch (item->item_type) {
+ case BTRC_ITEM_MEDIA:
+ {
+ // Parse name
+ jstring mediaName = sCallbackEnv->NewStringUTF((const char *) item->media.name);
+ // Parse UID
+ jbyteArray uidByteArray = sCallbackEnv->NewByteArray(
+ sizeof(uint8_t) * BTRC_UID_SIZE);
+ sCallbackEnv->SetByteArrayRegion(
+ uidByteArray, 0, BTRC_UID_SIZE * sizeof (uint8_t), (jbyte *) item->media.uid);
+
+ // Parse Attrs
+ jintArray attrIdArray = sCallbackEnv->NewIntArray(item->media.num_attrs);
+ jobjectArray attrValArray = sCallbackEnv->NewObjectArray(
+ item->media.num_attrs,
+ sCallbackEnv->FindClass("java/lang/String"),
+ 0);
+
+ for (int j = 0; j < item->media.num_attrs; j++) {
+ sCallbackEnv->SetIntArrayRegion(
+ attrIdArray, j, 1, (jint *)&(item->media.p_attrs[j].attr_id));
+ jstring attrValStr = sCallbackEnv->NewStringUTF(
+ (char *)(item->media.p_attrs[j].text));
+ sCallbackEnv->SetObjectArrayElement(
+ attrValArray, j, attrValStr);
+ sCallbackEnv->DeleteLocalRef(attrValStr);
+ }
+
+ jobject mediaObj = (jobject) sCallbackEnv->CallObjectMethod(
+ sCallbacksObj, method_createFromNativeMediaItem, uidByteArray,
+ (jint) item->media.type, mediaName, attrIdArray, attrValArray);
+ sCallbackEnv->DeleteLocalRef(uidByteArray);
+ sCallbackEnv->DeleteLocalRef(mediaName);
+ sCallbackEnv->DeleteLocalRef(attrIdArray);
+ sCallbackEnv->DeleteLocalRef(attrValArray);
+
+ if (!mediaObj) {
+ ALOGE("%s failed to create MediaItem for type ITEM_MEDIA", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetObjectArrayElement(folderItemArray, i, mediaObj);
+ sCallbackEnv->DeleteLocalRef(mediaObj);
+ break;
+ }
+
+ case BTRC_ITEM_FOLDER:
+ {
+ // Parse name
+ jstring folderName = sCallbackEnv->NewStringUTF((const char *) item->folder.name);
+ // Parse UID
+ jbyteArray uidByteArray = sCallbackEnv->NewByteArray(
+ sizeof(uint8_t) * BTRC_UID_SIZE);
+ sCallbackEnv->SetByteArrayRegion(
+ uidByteArray, 0, BTRC_UID_SIZE * sizeof (uint8_t), (jbyte *) item->folder.uid);
+
+ jobject folderObj = (jobject) sCallbackEnv->CallObjectMethod(
+ sCallbacksObj, method_createFromNativeFolderItem, uidByteArray,
+ (jint) item->folder.type, folderName, (jint) item->folder.playable);
+ sCallbackEnv->DeleteLocalRef(uidByteArray);
+ sCallbackEnv->DeleteLocalRef(folderName);
+
+ if (!folderObj) {
+ ALOGE("%s failed to create MediaItem for type ITEM_MEDIA", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetObjectArrayElement(folderItemArray, i, folderObj);
+ sCallbackEnv->DeleteLocalRef(folderObj);
+ break;
+ }
+
+ case BTRC_ITEM_PLAYER:
+ {
+ // Parse name
+ isPlayerListing = true;
+ jint id = (jint) item->player.player_id;
+ jint playerType = (jint) item->player.major_type;
+ jint playStatus = (jint) item->player.play_status;
+ jbyteArray featureBitArray = sCallbackEnv->NewByteArray(
+ BTRC_FEATURE_BIT_MASK_SIZE * sizeof (uint8_t));
+ if (!featureBitArray) {
+ ALOGE("%s failed to allocate featureBitArray", __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(playerItemArray);
+ sCallbackEnv->DeleteLocalRef(folderItemArray);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(
+ featureBitArray, 0, sizeof(uint8_t) * BTRC_FEATURE_BIT_MASK_SIZE,
+ (jbyte *) item->player.features);
+ jstring playerName = sCallbackEnv->NewStringUTF((const char *) item->player.name);
+ jobject playerObj = (jobject) sCallbackEnv->CallObjectMethod(
+ sCallbacksObj, method_createFromNativePlayerItem, id,
+ playerName, featureBitArray, playStatus, playerType);
+ sCallbackEnv->SetObjectArrayElement(playerItemArray, i, playerObj);
+
+ sCallbackEnv->DeleteLocalRef(featureBitArray);
+ sCallbackEnv->DeleteLocalRef(playerObj);
+ break;
+ }
+
+ default:
+ ALOGE("%s cannot understand type %d", __FUNCTION__, item->item_type);
+ }
+ ALOGI("%s inserted %d elements uptil now", __FUNCTION__, i);
+ }
+
+ ALOGI("%s returning the complete set now", __FUNCTION__);
+ if (isPlayerListing) {
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
+ playerItemArray);
+ } else {
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
+ folderItemArray);
+ }
+ if (isPlayerListing) {
+ sCallbackEnv->DeleteLocalRef(playerItemArray);
+ } else {
+ sCallbackEnv->DeleteLocalRef(folderItemArray);
+ }
+}
+
+static void btavrcp_change_path_callback(bt_bdaddr_t *bd_addr, uint8_t count) {
+ ALOGI("%s count %d", __FUNCTION__, count);
+
+ if (!checkCallbackThread()) {
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp, (jint) count);
+}
+
+static void btavrcp_set_browsed_player_callback(
+ bt_bdaddr_t *bd_addr, uint8_t num_items, uint8_t depth) {
+ ALOGI("%s items %d depth %d", __FUNCTION__, num_items, depth);
+
+ if (!checkCallbackThread()) {
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_handleSetBrowsedPlayerRsp, (jint) num_items, (jint) depth);
+}
+
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_passthrough_response_callback,
@@ -437,7 +633,10 @@
btavrcp_register_notification_absvol_callback,
btavrcp_track_changed_callback,
btavrcp_play_position_changed_callback,
- btavrcp_play_status_changed_callback
+ btavrcp_play_status_changed_callback,
+ btavrcp_get_folder_items_callback,
+ btavrcp_change_path_callback,
+ btavrcp_set_browsed_player_callback
};
static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -448,7 +647,7 @@
env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
method_onConnectionStateChanged =
- env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
+ env->GetMethodID(clazz, "onConnectionStateChanged", "(ZZ[B)V");
method_getRcFeatures =
env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
@@ -475,13 +674,40 @@
env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
method_handleplaystatuschanged =
- env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
+ env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
+
+ method_handleGetFolderItemsRsp =
+ env->GetMethodID(clazz, "handleGetFolderItemsRsp", "([Landroid/media/browse/MediaBrowser$MediaItem;)V");
+ method_handleGetPlayerItemsRsp =
+ env->GetMethodID(clazz, "handleGetPlayerItemsRsp",
+ "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+
+ method_createFromNativeMediaItem =
+ env->GetMethodID(clazz, "createFromNativeMediaItem",
+ "([BILjava/lang/String;[I[Ljava/lang/String;)Landroid/media/browse/MediaBrowser$MediaItem;");
+ method_createFromNativeFolderItem =
+ env->GetMethodID(clazz, "createFromNativeFolderItem",
+ "([BILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
+ method_createFromNativePlayerItem =
+ env->GetMethodID(clazz, "createFromNativePlayerItem",
+ "(ILjava/lang/String;[BII)Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;");
+ method_handleChangeFolderRsp =
+ env->GetMethodID(clazz, "handleChangeFolderRsp", "(I)V");
+ method_handleSetBrowsedPlayerRsp =
+ env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
ALOGI("%s: succeeds", __FUNCTION__);
}
static void initNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
bt_status_t status;
+ sEnv = env;
+
+ jclass tmpMediaItem = env->FindClass("android/media/browse/MediaBrowser$MediaItem");
+ class_MediaBrowser_MediaItem = (jclass) env->NewGlobalRef(tmpMediaItem);
+
+ jclass tmpBtPlayer = env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpPlayer");
+ class_AvrcpPlayer = (jclass) env->NewGlobalRef(tmpBtPlayer);
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -494,10 +720,10 @@
sBluetoothAvrcpInterface = NULL;
}
- if (mCallbacksObj != NULL) {
+ if (sCallbacksObj != NULL) {
ALOGW("Cleaning up Avrcp callback object");
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
+ env->DeleteGlobalRef(sCallbacksObj);
+ sCallbacksObj = NULL;
}
if ( (sBluetoothAvrcpInterface = (btrc_ctrl_interface_t *)
@@ -513,7 +739,7 @@
return;
}
- mCallbacksObj = env->NewGlobalRef(object);
+ sCallbacksObj = env->NewGlobalRef(object);
}
static void cleanupNative(JNIEnv *env, jobject object) {
@@ -529,9 +755,9 @@
sBluetoothAvrcpInterface = NULL;
}
- if (mCallbacksObj != NULL) {
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
+ if (sCallbacksObj != NULL) {
+ env->DeleteGlobalRef(sCallbacksObj);
+ sCallbacksObj = NULL;
}
}
@@ -685,6 +911,137 @@
env->ReleaseByteArrayElements(address, addr, 0);
}
+static void getNowPlayingListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+ jbyte items) {
+ bt_status_t status;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ ALOGV("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_now_playing_list_cmd(
+ (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getNowPlayingListNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void getFolderListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+ jbyte items) {
+ bt_status_t status;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ ALOGV("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_folder_list_cmd(
+ (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getFolderListNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void getPlayerListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+ jbyte items) {
+ bt_status_t status;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->get_player_list_cmd(
+ (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getPlayerListNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void changeFolderPathNative(JNIEnv *env, jobject object, jbyteArray address, jbyte direction,
+ jbyteArray uidarr) {
+ bt_status_t status;
+
+ jbyte *addr;
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ jbyte *uid;
+ uid = env->GetByteArrayElements(uidarr, NULL);
+ if (!uid) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->change_folder_path_cmd(
+ (bt_bdaddr_t *) addr, (uint8_t) direction, (uint8_t *) uid)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void setBrowsedPlayerNative(JNIEnv *env, jobject object, jbyteArray address, jint id) {
+ bt_status_t status;
+
+ jbyte *addr;
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->set_browsed_player_cmd(
+ (bt_bdaddr_t *) addr, (uint16_t) id)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void playItemNative(JNIEnv *env, jobject object, jbyteArray address, jbyte scope,
+ jbyteArray uidArr, jint uidCounter) {
+ bt_status_t status;
+
+ jbyte *addr;
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ jbyte *uid;
+ uid = env->GetByteArrayElements(uidArr, NULL);
+ if (!uid) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->play_item_cmd(
+ (bt_bdaddr_t *) addr, (uint8_t) scope, (uint8_t *) uid,
+ (uint16_t) uidCounter)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending playItemNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initNative", "()V", (void *) initNative},
@@ -695,11 +1052,18 @@
(void *) setPlayerApplicationSettingValuesNative},
{"sendAbsVolRspNative", "([BII)V",(void *) sendAbsVolRspNative},
{"sendRegisterAbsVolRspNative", "([BBII)V",(void *) sendRegisterAbsVolRspNative},
+ {"getNowPlayingListNative", "([BBB)V", (void *) getNowPlayingListNative},
+ {"getFolderListNative", "([BBB)V", (void *) getFolderListNative},
+ {"getPlayerListNative", "([BBB)V", (void *) getPlayerListNative},
+ {"changeFolderPathNative", "([BB[B)V", (void *) changeFolderPathNative},
+ {"playItemNative", "([BB[BI)V", (void *) playItemNative},
+ {"setBrowsedPlayerNative", "([BI)V", (void *) setBrowsedPlayerNative},
};
int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/AvrcpControllerService",
+ return jniRegisterNativeMethods(env,
+ "com/android/bluetooth/avrcpcontroller/AvrcpControllerService",
sMethods, NELEM(sMethods));
}
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index fa57e4f..70dcfea 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -1365,7 +1365,6 @@
if (!sGattIf) return;
set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
- error("advertiser address is: %d", sGattIf->advertiser);
sGattIf->advertiser->RegisterAdvertiser(base::Bind(&ble_advertiser_register_cb, uuid));
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 1d84d0f..4d0d2ca 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -17,13 +17,14 @@
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothAudioConfig;
-import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothA2dpSink;
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
+
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
import com.android.bluetooth.btservice.ProfileService;
@@ -192,12 +193,12 @@
*/
public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
if (mStateMachine != null) {
- if (keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY &&
- keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+ if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY &&
+ keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
- } else if ((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE ||
- keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP) &&
- keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+ } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE ||
+ keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) &&
+ keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
}
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 6e6e2af..c005f27 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -29,7 +29,6 @@
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
@@ -49,7 +48,7 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -694,16 +693,21 @@
return false;
}
+ // Utility Functions
boolean okToConnect(BluetoothDevice device) {
AdapterService adapterService = AdapterService.getAdapterService();
- boolean ret = true;
- //check if this is an incoming connection in Quiet mode.
- if((adapterService == null) ||
- ((adapterService.isQuietModeEnabled() == true) &&
- (mTargetDevice == null))){
- ret = false;
+ int priority = mService.getPriority(device);
+
+ // check priority and accept or reject the connection. if priority is undefined
+ // it is likely that our SDP has not completed and peer is initiating the
+ // connection. Allow this connection, provided the device is bonded
+ if((BluetoothProfile.PRIORITY_OFF < priority) ||
+ ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
+ (device.getBondState() != BluetoothDevice.BOND_NONE))){
+ return true;
}
- return ret;
+ logw("okToConnect not OK to connect " + device);
+ return false;
}
synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -802,6 +806,7 @@
this.type = type;
}
}
+
/** Handles A2DP connection state change intent broadcasts. */
private class IntentBroadcastHandler extends Handler {
@@ -833,11 +838,11 @@
if ((avrcpCtrlService != null) && (mDevice != null) &&
(avrcpCtrlService.getConnectedDevices().contains(mDevice))){
avrcpCtrlService.sendPassThroughCmd(mDevice,
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
- BluetoothAvrcpController.KEY_STATE_PRESSED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+ AvrcpControllerService.KEY_STATE_PRESSED);
avrcpCtrlService.sendPassThroughCmd(mDevice,
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
- BluetoothAvrcpController.KEY_STATE_RELEASED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+ AvrcpControllerService.KEY_STATE_RELEASED);
log(" SendPassThruPlay command sent - ");
return true;
} else {
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
index fb62c95..9a84eb0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
@@ -17,7 +17,6 @@
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioManager;
@@ -25,7 +24,7 @@
import android.os.Message;
import android.util.Log;
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.R;
import com.android.internal.util.IState;
import com.android.internal.util.State;
@@ -209,12 +208,12 @@
}
avrcpService.sendPassThroughCmd(
avrcpService.getConnectedDevices().get(0),
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
- BluetoothAvrcpController.KEY_STATE_PRESSED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_PRESSED);
avrcpService.sendPassThroughCmd(
avrcpService.getConnectedDevices().get(0),
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
- BluetoothAvrcpController.KEY_STATE_RELEASED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_RELEASED);
} else {
Log.e(TAG, "Passthrough not sent, connection un-available.");
}
@@ -233,12 +232,12 @@
}
avrcpService.sendPassThroughCmd(
avrcpService.getConnectedDevices().get(0),
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
- BluetoothAvrcpController.KEY_STATE_PRESSED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+ AvrcpControllerService.KEY_STATE_PRESSED);
avrcpService.sendPassThroughCmd(
avrcpService.getConnectedDevices().get(0),
- BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
- BluetoothAvrcpController.KEY_STATE_RELEASED);
+ AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+ AvrcpControllerService.KEY_STATE_RELEASED);
} else {
Log.e(TAG, "Passthrough not sent, connection un-available.");
}
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
index 7ee0276..f663571 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -24,8 +24,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
@@ -33,44 +35,54 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.ResultReceiver;
import android.service.media.MediaBrowserService;
import android.util.Pair;
import android.util.Log;
import com.android.bluetooth.R;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.BrowseTree;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class A2dpMediaBrowserService extends MediaBrowserService {
private static final String TAG = "A2dpMediaBrowserService";
- private static final String MEDIA_ID_ROOT = "__ROOT__";
private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
private static final float PLAYBACK_SPEED = 1.0f;
// Message sent when A2DP device is disconnected.
private static final int MSG_DEVICE_DISCONNECT = 0;
- // Message snet when the AVRCP profile is disconnected = 1;
- private static final int MSG_PROFILE_DISCONNECT = 1;
// Message sent when A2DP device is connected.
private static final int MSG_DEVICE_CONNECT = 2;
- // Message sent when AVRCP profile is connected (note AVRCP profile may be connected before or
- // after A2DP device is connected).
- private static final int MSG_PROFILE_CONNECT = 3;
// Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
private static final int MSG_TRACK = 4;
// Internal message sent to trigger a AVRCP action.
private static final int MSG_AVRCP_PASSTHRU = 5;
+ // Message sent when AVRCP browse is connected.
+ private static final int MSG_DEVICE_BROWSE_CONNECT = 6;
+ // Message sent when AVRCP browse is disconnected.
+ private static final int MSG_DEVICE_BROWSE_DISCONNECT = 7;
+ // Message sent when folder list is fetched.
+ private static final int MSG_FOLDER_LIST = 9;
private MediaSession mSession;
private MediaMetadata mA2dpMetadata;
- private BluetoothAdapter mAdapter;
- private BluetoothAvrcpController mAvrcpProfile;
+ private AvrcpControllerService mAvrcpCtrlSrvc;
+ private boolean mBrowseConnected = false;
private BluetoothDevice mA2dpDevice = null;
private Handler mAvrcpCommandQueue;
+ private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
+ private static final List<MediaItem> mEmptyList = new ArrayList<MediaItem>();
+
+ // Browsing related structures.
+ private List<MediaItem> mNowPlayingList = null;
private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
| PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
@@ -95,15 +107,9 @@
case MSG_DEVICE_CONNECT:
inst.msgDeviceConnect((BluetoothDevice) msg.obj);
break;
- case MSG_PROFILE_CONNECT:
- inst.msgProfileConnect((BluetoothProfile) msg.obj);
- break;
case MSG_DEVICE_DISCONNECT:
inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
break;
- case MSG_PROFILE_DISCONNECT:
- inst.msgProfileDisconnect();
- break;
case MSG_TRACK:
Pair<PlaybackState, MediaMetadata> pair =
(Pair<PlaybackState, MediaMetadata>) (msg.obj);
@@ -112,6 +118,17 @@
case MSG_AVRCP_PASSTHRU:
inst.msgPassThru((int) msg.obj);
break;
+ case MSG_DEVICE_BROWSE_CONNECT:
+ inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
+ break;
+ case MSG_DEVICE_BROWSE_DISCONNECT:
+ inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
+ break;
+ case MSG_FOLDER_LIST:
+ inst.msgFolderList((Intent) msg.obj);
+ break;
+ default:
+ Log.e(TAG, "Message not handled " + msg);
}
}
}
@@ -120,6 +137,7 @@
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
+
mSession = new MediaSession(this, TAG);
setSessionToken(mSession.getSessionToken());
mSession.setCallback(mSessionCallbacks);
@@ -127,13 +145,18 @@
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.AVRCP_CONTROLLER);
+ refreshInitialPlayingState();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+ filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
+ filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
+ filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
registerReceiver(mBtReceiver, filter);
+
+ synchronized (this) {
+ mParentIdToRequestMap.clear();
+ }
}
@Override
@@ -146,37 +169,32 @@
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
- return new BrowserRoot(MEDIA_ID_ROOT, null);
+ return new BrowserRoot(BrowseTree.ROOT, null);
}
@Override
- public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+ public synchronized void onLoadChildren(
+ final String parentMediaId, final Result<List<MediaItem>> result) {
+ if (mAvrcpCtrlSrvc == null) {
+ Log.e(TAG, "AVRCP not yet connected.");
+ result.sendResult(mEmptyList);
+ return;
+ }
+
Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
- List<MediaItem> items = new ArrayList<MediaItem>();
- result.sendResult(items);
+ mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff);
+
+ // Since we are using this thread from a binder thread we should make sure that
+ // we synchronize against other such asynchronous calls.
+ synchronized (this) {
+ mParentIdToRequestMap.put(parentMediaId, result);
+ }
+ result.detach();
}
- BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "onServiceConnected");
- if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
- mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_CONNECT, proxy).sendToTarget();
- List<BluetoothDevice> devices = proxy.getConnectedDevices();
- if (devices != null && devices.size() > 0) {
- BluetoothDevice device = devices.get(0);
- Log.d(TAG, "got AVRCP device " + device);
- }
- }
- }
-
- public void onServiceDisconnected(int profile) {
- Log.d(TAG, "onServiceDisconnected " + profile);
- if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
- mAvrcpProfile = null;
- mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_DISCONNECT).sendToTarget();
- }
- }
- };
+ @Override
+ public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
+ }
// Media Session Stuff.
private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
@@ -184,7 +202,7 @@
public void onPlay() {
Log.d(TAG, "onPlay");
mAvrcpCommandQueue.obtainMessage(
- MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY).sendToTarget();
+ MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@@ -192,7 +210,7 @@
public void onPause() {
Log.d(TAG, "onPause");
mAvrcpCommandQueue.obtainMessage(
- MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
+ MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@@ -200,7 +218,7 @@
public void onSkipToNext() {
Log.d(TAG, "onSkipToNext");
mAvrcpCommandQueue.obtainMessage(
- MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD)
+ MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD)
.sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@@ -210,7 +228,7 @@
Log.d(TAG, "onSkipToPrevious");
mAvrcpCommandQueue.obtainMessage(
- MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD)
+ MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD)
.sendToTarget();
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
}
@@ -243,9 +261,16 @@
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
- Log.d(TAG, "onPlayFromMediaId mediaId=" + mediaId + " extras=" + extras);
- }
+ synchronized (A2dpMediaBrowserService.this) {
+ // Play the item if possible.
+ mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
+ // Since we request explicit playback here we should start the updates to UI.
+ mAvrcpCtrlSrvc.startAvrcpUpdates();
+ }
+
+ // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+ }
};
private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
@@ -268,40 +293,49 @@
// Set the playback state to unconnected.
mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
}
- } else if (BluetoothAvrcpController.ACTION_TRACK_EVENT.equals(action)) {
+ } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
+ action)) {
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_DEVICE_BROWSE_CONNECT, btDev).sendToTarget();
+ } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_DEVICE_BROWSE_DISCONNECT, btDev).sendToTarget();
+ }
+ } else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
PlaybackState pbb =
- intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_PLAYBACK);
+ intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
MediaMetadata mmd =
- intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_METADATA);
+ intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
mAvrcpCommandQueue.obtainMessage(
MSG_TRACK, new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
+ } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
+ mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
}
}
};
- private void msgDeviceConnect(BluetoothDevice device) {
+ private synchronized void msgDeviceConnect(BluetoothDevice device) {
Log.d(TAG, "msgDeviceConnect");
// We are connected to a new device via A2DP now.
mA2dpDevice = device;
- refreshInitialPlayingState();
- }
-
- private void msgProfileConnect(BluetoothProfile profile) {
- Log.d(TAG, "msgProfileConnect");
- if (profile != null) {
- mAvrcpProfile = (BluetoothAvrcpController) profile;
+ mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
+ if (mAvrcpCtrlSrvc == null) {
+ Log.e(TAG, "!!!AVRCP Controller cannot be null");
+ return;
}
refreshInitialPlayingState();
}
+
// Refresh the UI if we have a connected device and AVRCP is initialized.
- private void refreshInitialPlayingState() {
- if (mAvrcpProfile == null || mA2dpDevice == null) {
- Log.d(TAG, "AVRCP Profile " + mAvrcpProfile + " device " + mA2dpDevice);
+ private synchronized void refreshInitialPlayingState() {
+ if (mA2dpDevice == null) {
+ Log.d(TAG, "device " + mA2dpDevice);
return;
}
- List<BluetoothDevice> devices = mAvrcpProfile.getConnectedDevices();
+ List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
if (devices.size() == 0) {
Log.w(TAG, "No devices connected yet");
return;
@@ -309,17 +343,18 @@
if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
Log.e(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
+ return;
}
mA2dpDevice = devices.get(0);
- PlaybackState playbackState = mAvrcpProfile.getPlaybackState(mA2dpDevice);
+ PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
// Add actions required for playback and rebuild the object.
PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
playbackState = pbb.setActions(mTransportControlFlags).build();
- MediaMetadata mediaMetadata = mAvrcpProfile.getMetadata(mA2dpDevice);
+ MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
- mSession.setMetadata(mAvrcpProfile.getMetadata(mA2dpDevice));
+ mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
mSession.setPlaybackState(playbackState);
}
@@ -341,21 +376,10 @@
.setActions(mTransportControlFlags)
.setErrorMessage(getString(R.string.bluetooth_disconnected));
mSession.setPlaybackState(pbb.build());
- }
- private void msgProfileDisconnect() {
- Log.d(TAG, "msgProfileDisconnect");
- // The profile is disconnected - even if the device is still connected we cannot really have
- // a functioning UI so reset the session.
- mAvrcpProfile = null;
-
- // Unset the session.
- PlaybackState.Builder pbb = new PlaybackState.Builder();
- pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- PLAYBACK_SPEED)
- .setActions(mTransportControlFlags)
- .setErrorMessage(getString(R.string.bluetooth_disconnected));
- mSession.setPlaybackState(pbb.build());
+ // Set device to null.
+ mA2dpDevice = null;
+ mBrowseConnected = false;
}
private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
@@ -388,7 +412,7 @@
}
}
- private void msgPassThru(int cmd) {
+ private synchronized void msgPassThru(int cmd) {
Log.d(TAG, "msgPassThru " + cmd);
if (mA2dpDevice == null) {
// We should have already disconnected - ignore this message.
@@ -396,17 +420,54 @@
return;
}
- if (mAvrcpProfile == null) {
- // We may be disconnected with the profile but there is not much we can do for now but
- // to wait for the profile to come back up.
- Log.e(TAG, "Profile disconnected; ignoring.");
+ // Send the pass through.
+ mAvrcpCtrlSrvc.sendPassThroughCmd(
+ mA2dpDevice, cmd, AvrcpControllerService.KEY_STATE_PRESSED);
+ mAvrcpCtrlSrvc.sendPassThroughCmd(
+ mA2dpDevice, cmd, AvrcpControllerService.KEY_STATE_RELEASED);
+ }
+
+ private void msgDeviceBrowseConnect(BluetoothDevice device) {
+ Log.d(TAG, "msgDeviceBrowseConnect device " + device);
+ // We should already be connected to this device over A2DP.
+ if (!device.equals(mA2dpDevice)) {
+ Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice +
+ " browse " + device);
return;
}
+ mBrowseConnected = true;
+ }
- // Send the pass through.
- mAvrcpProfile.sendPassThroughCmd(
- mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_PRESSED);
- mAvrcpProfile.sendPassThroughCmd(
- mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_RELEASED);
+ private void msgFolderList(Intent intent) {
+ // Parse the folder list for children list and id.
+ List<Parcelable> extraParcelableList =
+ (ArrayList<Parcelable>) intent.getParcelableArrayListExtra(
+ AvrcpControllerService.EXTRA_FOLDER_LIST);
+ List<MediaItem> folderList = new ArrayList<MediaItem>();
+ for (Parcelable p : extraParcelableList) {
+ folderList.add((MediaItem) p);
+ }
+
+ String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
+ Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
+ synchronized (this) {
+ Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
+ if (results == null) {
+ Log.w(TAG, "Request no longer exists, hence ignoring reply!");
+ return;
+ }
+ results.sendResult(folderList);
+ }
+ }
+
+ private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
+ Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
+ // Disconnect only if mA2dpDevice is non null
+ if (!device.equals(mA2dpDevice)) {
+ Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice +
+ " browse " + device);
+ return;
+ }
+ mBrowseConnected = false;
}
}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
deleted file mode 100644
index 6675124..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
+++ /dev/null
@@ -1,964 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothAvrcpController;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.media.AudioManager;
-
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.Utils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.nio.charset.Charset;
-import java.nio.ByteBuffer;
-
-/**
- * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
- * @hide
- */
-public class AvrcpControllerService extends ProfileService {
- private static final boolean DBG = AvrcpControllerConstants.DBG;
- private static final boolean VDBG = AvrcpControllerConstants.VDBG;
- private static final String TAG = "AvrcpControllerService";
-
-/*
- * Messages handled by mHandler
- */
-
- RemoteDevice mAvrcpRemoteDevice;
- RemoteMediaPlayers mRemoteMediaPlayers;
- NowPlaying mRemoteNowPlayingList;
-
- private AvrcpMessageHandler mHandler;
- private static AvrcpControllerService sAvrcpControllerService;
- private static AudioManager mAudioManager;
-
- private final ArrayList<BluetoothDevice> mConnectedDevices
- = new ArrayList<BluetoothDevice>();
-
- static {
- classInitNative();
- }
-
- public AvrcpControllerService() {
- initNative();
- }
-
- protected String getName() {
- return TAG;
- }
-
- protected IProfileServiceBinder initBinder() {
- return new BluetoothAvrcpControllerBinder(this);
- }
-
- protected boolean start() {
- HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
- thread.start();
- Looper looper = thread.getLooper();
- mHandler = new AvrcpMessageHandler(looper);
-
- setAvrcpControllerService(this);
- mAudioManager = (AudioManager)sAvrcpControllerService.
- getSystemService(Context.AUDIO_SERVICE);
- return true;
- }
-
- protected void resetRemoteData() {
- try {
- unregisterReceiver(mBroadcastReceiver);
- }
- catch (IllegalArgumentException e) {
- Log.e(TAG,"Receiver not registered");
- }
- if(mAvrcpRemoteDevice != null) {
- mAvrcpRemoteDevice.cleanup();
- mAvrcpRemoteDevice = null;
- }
- if(mRemoteMediaPlayers != null) {
- mRemoteMediaPlayers.cleanup();
- mRemoteMediaPlayers = null;
- }
- if(mRemoteNowPlayingList != null) {
- mRemoteNowPlayingList.cleanup();
- mRemoteNowPlayingList = null;
- }
- }
- protected boolean stop() {
- if (mHandler != null) {
- mHandler.removeCallbacksAndMessages(null);
- Looper looper = mHandler.getLooper();
- if (looper != null) {
- looper.quit();
- }
- }
- resetRemoteData();
- return true;
- }
-
- protected boolean cleanup() {
- if (mHandler != null) {
- mHandler.removeCallbacksAndMessages(null);
- Looper looper = mHandler.getLooper();
- if (looper != null) looper.quit();
- }
- resetRemoteData();
- clearAvrcpControllerService();
- cleanupNative();
-
- return true;
- }
-
- //API Methods
-
- public static synchronized AvrcpControllerService getAvrcpControllerService(){
- if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
- if (DBG) Log.d(TAG, "getAvrcpControllerService(): returning "
- + sAvrcpControllerService);
- return sAvrcpControllerService;
- }
- if (DBG) {
- if (sAvrcpControllerService == null) {
- Log.d(TAG, "getAvrcpControllerService(): service is NULL");
- } else if (!(sAvrcpControllerService.isAvailable())) {
- Log.d(TAG,"getAvrcpControllerService(): service is not available");
- }
- }
- return null;
- }
-
- private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
- if (instance != null && instance.isAvailable()) {
- if (DBG) Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
- sAvrcpControllerService = instance;
- } else {
- if (DBG) {
- if (sAvrcpControllerService == null) {
- Log.d(TAG, "setAvrcpControllerService(): service not available");
- } else if (!sAvrcpControllerService.isAvailable()) {
- Log.d(TAG,"setAvrcpControllerService(): service is cleaning up");
- }
- }
- }
- }
-
- private static synchronized void clearAvrcpControllerService() {
- sAvrcpControllerService = null;
- }
-
- public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mConnectedDevices;
- }
-
- List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- for (int i = 0; i < states.length; i++) {
- if (states[i] == BluetoothProfile.STATE_CONNECTED) {
- return mConnectedDevices;
- }
- }
- return new ArrayList<BluetoothDevice>();
- }
-
- int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
- }
-
- public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
- if (device == null) {
- throw new NullPointerException("device == null");
- }
- if (!(mConnectedDevices.contains(device))) {
- for (BluetoothDevice cdevice : mConnectedDevices) {
- Log.e(TAG, "Device: " + cdevice);
- }
- Log.e(TAG," Device does not match " + device);
- return;
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_SEND_GROUP_NAVIGATION_CMD,keyCode, keyState, device);
- mHandler.sendMessage(msg);
- }
-
- public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
- if (device == null) {
- throw new NullPointerException("device == null");
- }
- if (!(mConnectedDevices.contains(device))) {
- Log.d(TAG," Device does not match");
- return;
- }
- if ((mAvrcpRemoteDevice == null)||
- (mAvrcpRemoteDevice.mRemoteFeatures == AvrcpControllerConstants.BTRC_FEAT_NONE)||
- (mRemoteMediaPlayers == null) ||
- (mRemoteMediaPlayers.getAddressedPlayer() == null)){
- Log.d(TAG," Device connected but PlayState not present ");
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
- keyCode, keyState, device);
- mHandler.sendMessage(msg);
- return;
- }
- boolean sendCommand = false;
- switch(keyCode) {
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY:
- sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PAUSED) ||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PLAYING);
- break;
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE:
- /*
- * allowing pause command in pause state to handle A2DP Sink Concurrency
- * If call is ongoing and Start is initiated from remote, we will send pause again
- * If acquireFocus fails, we will send Pause again
- * To Stop sending multiple Pause, check in application.
- */
- sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PAUSED)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_REV_SEEK);
- break;
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP:
- sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_REV_SEEK)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
- (mRemoteMediaPlayers.getPlayStatus() ==
- AvrcpControllerConstants.PLAY_STATUS_PAUSED);
- break;
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD:
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD:
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_FF:
- case BluetoothAvrcpController.PASS_THRU_CMD_ID_REWIND:
- sendCommand = true; // we can send this command in all states
- break;
- }
- if (sendCommand) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
- keyCode, keyState, device);
- mHandler.sendMessage(msg);
- }
- else {
- Log.e(TAG," Not in right state, don't send Pass Thru cmd ");
- }
- }
-
- public void startAvrcpUpdates() {
- mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
- }
-
- public void stopAvrcpUpdates() {
- mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
- }
-
- public MediaMetadata getMetaData(BluetoothDevice device) {
- Log.d(TAG, "getMetaData = ");
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if((mRemoteNowPlayingList != null) && (mRemoteNowPlayingList.getCurrentTrack() != null)) {
- return getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0);
- }
- else
- return null;
- }
- public PlaybackState getPlaybackState(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "getPlayBackState device = "+ device);
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getCurrentPlayBackState();
- }
- public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "getPlayerApplicationSetting ");
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getCurrentPlayerAppSetting();
- }
- public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
- if ((mAvrcpRemoteDevice == null)||(mRemoteMediaPlayers == null)) {
- return false;
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- /*
- * We have to extract values from BluetoothAvrcpPlayerSettings
- */
- int mSettings = plAppSetting.getSettings();
- int numAttributes = 0;
- /* calculate number of attributes in request */
- while(mSettings > 0) {
- numAttributes += ((mSettings & 0x01)!= 0)?1: 0;
- mSettings = mSettings >> 1;
- }
- byte[] attribArray = new byte [2*numAttributes];
- mSettings = plAppSetting.getSettings();
- /*
- * Now we will flatten it <id, val>
- */
- int i = 0;
- if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
- attribArray[i++] = AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS;
- attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
- BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER, plAppSetting.
- getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER));
- }
- if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
- attribArray[i++] = AvrcpControllerConstants.ATTRIB_REPEAT_STATUS;
- attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
- BluetoothAvrcpPlayerSettings.SETTING_REPEAT, plAppSetting.
- getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT));
- }
- if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
- attribArray[i++] = AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS;
- attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
- BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE, plAppSetting.
- getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE));
- }
- if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
- attribArray[i++] = AvrcpControllerConstants.ATTRIB_SCAN_STATUS;
- attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
- BluetoothAvrcpPlayerSettings.SETTING_SCAN, plAppSetting.
- getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN));
- }
- boolean isSettingSupported = mRemoteMediaPlayers.getAddressedPlayer().
- isPlayerAppSettingSupported((byte)numAttributes, attribArray);
- if(isSettingSupported) {
- ByteBuffer bb = ByteBuffer.wrap(attribArray, 0, (2*numAttributes));
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS, numAttributes, 0, bb);
- mHandler.sendMessage(msg);
- }
- return isSettingSupported;
- }
-
- //Binder object: Must be static class or memory leak may occur
- private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
- implements IProfileServiceBinder {
- private AvrcpControllerService mService;
-
- private AvrcpControllerService getService() {
- if (!Utils.checkCaller()) {
- Log.w(TAG,"AVRCP call not allowed for non-active user");
- return null;
- }
-
- if (mService != null && mService.isAvailable()) {
- return mService;
- }
- return null;
- }
-
- BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
- mService = svc;
- }
-
- public boolean cleanup() {
- mService = null;
- return true;
- }
-
- public List<BluetoothDevice> getConnectedDevices() {
- AvrcpControllerService service = getService();
- if (service == null) return new ArrayList<BluetoothDevice>(0);
- return service.getConnectedDevices();
- }
-
- public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- AvrcpControllerService service = getService();
- if (service == null) return new ArrayList<BluetoothDevice>(0);
- return service.getDevicesMatchingConnectionStates(states);
- }
-
- public int getConnectionState(BluetoothDevice device) {
- AvrcpControllerService service = getService();
- if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
- return service.getConnectionState(device);
- }
-
- public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG,"Binder Call: sendPassThroughCmd");
- AvrcpControllerService service = getService();
- if (service == null) return;
- service.sendPassThroughCmd(device, keyCode, keyState);
- }
-
- @Override
- public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG,"Binder Call: sendGroupNavigationCmd");
- AvrcpControllerService service = getService();
- if (service == null) return;
- service.sendGroupNavigationCmd(device, keyCode, keyState);
- }
-
- @Override
- public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
- Log.v(TAG,"Binder Call: getPlayerApplicationSetting ");
- AvrcpControllerService service = getService();
- if (service == null) return null;
- return service.getPlayerSettings(device);
- }
-
- @Override
- public MediaMetadata getMetadata(BluetoothDevice device) {
- Log.v(TAG,"Binder Call: getMetaData ");
- AvrcpControllerService service = getService();
- if (service == null) return null;
- return service.getMetaData(device);
- }
-
- @Override
- public PlaybackState getPlaybackState(BluetoothDevice device) {
- Log.v(TAG,"Binder Call: getPlaybackState");
- AvrcpControllerService service = getService();
- if (service == null) return null;
- return service.getPlaybackState(device);
- }
-
- @Override
- public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
- Log.v(TAG,"Binder Call: setPlayerApplicationSetting " );
- AvrcpControllerService service = getService();
- if (service == null) return false;
- return service.setPlayerApplicationSetting(plAppSetting);
- }
- };
-
- private String utf8ToString(byte[] input)
- {
- Charset UTF8_CHARSET = Charset.forName("UTF-8");
- return new String(input,UTF8_CHARSET);
- }
- private int asciiToInt(int len, byte[] array)
- {
- return Integer.parseInt(utf8ToString(array));
- }
- private BluetoothAvrcpPlayerSettings getCurrentPlayerAppSetting() {
- if((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null))
- return null;
- return mRemoteMediaPlayers.getAddressedPlayer().getSupportedPlayerAppSetting();
- }
- private PlaybackState getCurrentPlayBackState() {
- if ((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) {
- return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- 0).build();
- }
- return AvrcpUtils.mapBtPlayStatustoPlayBackState(
- mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
- mRemoteMediaPlayers.getAddressedPlayer().mPlayTime);
- }
- private MediaMetadata getCurrentMetaData(int scope, int trackId) {
- /* if scope is now playing */
- if(scope == AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING) {
- if((mRemoteNowPlayingList == null) || (mRemoteNowPlayingList.
- getTrackFromId(trackId) == null))
- return null;
- TrackInfo mNowPlayingTrack = mRemoteNowPlayingList.getTrackFromId(trackId);
- return AvrcpUtils.getMediaMetaData(mNowPlayingTrack);
- }
- /* if scope is now playing */
- else if(scope == AvrcpControllerConstants.AVRCP_SCOPE_VFS) {
- /* TODO for browsing */
- }
- return null;
- }
- private void broadcastMetaDataChanged(MediaMetadata mMetaData) {
- Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
- intent.putExtra(BluetoothAvrcpController.EXTRA_METADATA, mMetaData);
- if(DBG) Log.d(TAG," broadcastMetaDataChanged = " +
- AvrcpUtils.displayMetaData(mMetaData));
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
- private void broadcastPlayBackStateChanged(PlaybackState state) {
- Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
- intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYBACK, state);
- if(DBG) Log.d(TAG," broadcastPlayBackStateChanged = " + state.toString());
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
- private void broadcastPlayerAppSettingChanged(BluetoothAvrcpPlayerSettings mPlAppSetting) {
- Intent intent = new Intent(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
- intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYER_SETTING, mPlAppSetting);
- if(DBG) Log.d(TAG," broadcastPlayerAppSettingChanged = " +
- AvrcpUtils.displayBluetoothAvrcpSettings(mPlAppSetting));
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
- /** Handles Avrcp messages. */
- private final class AvrcpMessageHandler extends Handler {
- private boolean mBroadcastMetadata = false;
-
- private AvrcpMessageHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- Log.d(TAG," HandleMessage: "+ AvrcpControllerConstants.dumpMessageString(msg.what) +
- " Remote Connected " + !mConnectedDevices.isEmpty());
- A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
- switch (msg.what) {
- case AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS:
- // Any messages hence forth about play pos/play status/song info will be ignored.
- if(mRemoteMediaPlayers != null) {
- // Mock the current state to *look like* it is paused. The remote play state is
- // still cached in mRemoteMediaPlayers and will be restored when the
- // startAvrcpUpdates is called again.
- broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
- ((byte) AvrcpControllerConstants.PLAY_STATUS_PAUSED,
- mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
- }
- mBroadcastMetadata = false;
- break;
- case AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS:
- // Any messages hence forth about play pos/play status/song info will be sent.
- if(mRemoteMediaPlayers != null) {
- broadcastPlayBackStateChanged(getCurrentPlayBackState());
- broadcastMetaDataChanged(
- getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0));
- }
- mBroadcastMetadata = true;
- break;
- case AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD:
- BluetoothDevice device = (BluetoothDevice)msg.obj;
- sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
- if((a2dpSinkService != null)&&(!mConnectedDevices.isEmpty())) {
- Log.d(TAG," inform AVRCP Commands to A2DP Sink ");
- a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
- }
- break;
- case AvrcpControllerConstants.MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- BluetoothDevice peerDevice = (BluetoothDevice)msg.obj;
- sendGroupNavigationCommandNative(getByteAddress(peerDevice), msg.arg1, msg.arg2);
- break;
- case AvrcpControllerConstants.MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
- byte numAttributes = (byte)msg.arg1;
- ByteBuffer bbRsp = (ByteBuffer)msg.obj;
- byte[] attributeIds = new byte [numAttributes];
- byte[] attributeVals = new byte [numAttributes];
- for(int i = 0; (bbRsp.hasRemaining())&&(i < numAttributes); i++) {
- attributeIds[i] = bbRsp.get();
- attributeVals[i] = bbRsp.get();
- }
- setPlayerApplicationSettingValuesNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
- numAttributes, attributeIds, attributeVals);
- break;
-
- case AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE:
- int newState = msg.arg1;
- int oldState = msg.arg2;
- BluetoothDevice rtDevice = (BluetoothDevice)msg.obj;
- if ((newState == BluetoothProfile.STATE_CONNECTED) &&
- (oldState == BluetoothProfile.STATE_DISCONNECTED)) {
- /* We create RemoteDevice and MediaPlayerList here
- * Now playing list after RC features
- */
- if(mAvrcpRemoteDevice == null){
- mAvrcpRemoteDevice = new RemoteDevice(rtDevice);
- /* Remote will have a player irrespective of AVRCP Version
- * Create a Default player, we will add entries in case Browsing
- * is supported by remote
- */
- if(mRemoteMediaPlayers == null) {
- mRemoteMediaPlayers = new RemoteMediaPlayers(mAvrcpRemoteDevice);
- PlayerInfo mPlayer = new PlayerInfo();
- mPlayer.mPlayerId = 0;
- mRemoteMediaPlayers.addPlayer(mPlayer);
- mRemoteMediaPlayers.setAddressedPlayer(mPlayer);
- }
- }
- }
- else if ((newState == BluetoothProfile.STATE_DISCONNECTED) &&
- (oldState == BluetoothProfile.STATE_CONNECTED)) /* connection down */
- {
- resetRemoteData();
- mHandler.removeCallbacksAndMessages(null);
- }
- /*
- * Send intent now
- */
- Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
-// intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES:
- if(mAvrcpRemoteDevice == null)
- break;
- mAvrcpRemoteDevice.mRemoteFeatures = msg.arg1;
- /* in case of AVRCP version < 1.3, no need to add track info */
- if(mAvrcpRemoteDevice.isMetaDataSupported()) {
- if(mRemoteNowPlayingList == null)
- mRemoteNowPlayingList = new NowPlaying(mAvrcpRemoteDevice);
- TrackInfo mTrack = new TrackInfo();
- /* First element of NowPlayingList will be current Track
- * for 1.3 this will be the only song
- * for >= 1.4, others songs will have non-zero UID
- */
- mTrack.mItemUid = 0;
- mRemoteNowPlayingList.addTrack(mTrack);
- mRemoteNowPlayingList.setCurrTrack(mTrack);
- }
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- mAvrcpRemoteDevice.mAbsVolNotificationState =
- AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
- setAbsVolume(msg.arg1, msg.arg2);
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- /* start BroadcastReceiver now */
- IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
- mAvrcpRemoteDevice.mNotificationLabel = msg.arg1;
- mAvrcpRemoteDevice.mAbsVolNotificationState =
- AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
- registerReceiver(mBroadcastReceiver, filter);
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume);
- Log.d(TAG," Sending Interim Response = "+ percentageVol + " label " + msg.arg1);
- sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
- (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_INTERIM, percentageVol,
- mAvrcpRemoteDevice.mNotificationLabel);
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_TRACK_CHANGED:
- if(mRemoteNowPlayingList != null) {
- mRemoteNowPlayingList.updateCurrentTrack((TrackInfo)msg.obj);
-
- if (!mBroadcastMetadata) {
- Log.d(TAG, "Metadata is not broadcasted, ignoring.");
- return;
- }
-
- broadcastMetaDataChanged(AvrcpUtils.getMediaMetaData
- (mRemoteNowPlayingList.getCurrentTrack()));
- }
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_POS_CHANGED:
- if(mRemoteMediaPlayers != null) {
- mRemoteMediaPlayers.getAddressedPlayer().mPlayTime = msg.arg2;
-
- if (!mBroadcastMetadata) {
- Log.d(TAG, "Metadata is not broadcasted, ignoring.");
- return;
- }
-
- broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
- (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
- mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
- }
- if(mRemoteNowPlayingList != null) {
- mRemoteNowPlayingList.getCurrentTrack().mTrackLen = msg.arg1;
- }
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- if(mRemoteMediaPlayers != null) {
- int status = msg.arg1;
- mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus = (byte) status;
- if (status == AvrcpControllerConstants.PLAY_STATUS_PLAYING) {
- a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), true);
- } else if (status == AvrcpControllerConstants.PLAY_STATUS_PAUSED ||
- status == AvrcpControllerConstants.PLAY_STATUS_STOPPED) {
- a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), false);
- }
-
- if (mBroadcastMetadata) {
- broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
- (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
- mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
- } else {
- Log.d(TAG, "Metadata is not broadcasted, ignoring.");
- return;
- }
- }
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
- if(mRemoteMediaPlayers != null)
- mRemoteMediaPlayers.getAddressedPlayer().
- setSupportedPlayerAppSetting((ByteBuffer)msg.obj);
- break;
- case AvrcpControllerConstants.MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
- if(mRemoteMediaPlayers != null) {
- mRemoteMediaPlayers.getAddressedPlayer().
- updatePlayerAppSetting((ByteBuffer)msg.obj);
- broadcastPlayerAppSettingChanged(getCurrentPlayerAppSetting());
- }
- break;
- }
- }
- }
-
- private void setAbsVolume(int absVol, int label)
- {
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- if (mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd) {
- int newIndex = (maxVolume*absVol)/AvrcpControllerConstants.ABS_VOL_BASE;
- Log.d(TAG," setAbsVolume ="+absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
- " new = "+newIndex);
- /*
- * In some cases change in percentage is not sufficient enough to warrant
- * change in index values which are in range of 0-15. For such cases
- * no action is required
- */
- if (newIndex != currIndex) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
- AudioManager.FLAG_SHOW_UI);
- }
- }
- else {
- mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd = true;
- absVol = (currIndex*AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume;
- Log.d(TAG," SetAbsVol recvd for first time, respond with " + absVol);
- }
- sendAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), absVol, label);
- }
-
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
- int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (streamType == AudioManager.STREAM_MUSIC) {
- int streamValue = intent
- .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
- int streamPrevValue = intent.getIntExtra(
- AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
- if (streamValue != -1 && streamValue != streamPrevValue) {
- if ((mAvrcpRemoteDevice == null)
- ||((mAvrcpRemoteDevice.mRemoteFeatures &
- AvrcpControllerConstants.BTRC_FEAT_ABSOLUTE_VOLUME) == 0)
- ||(mConnectedDevices.isEmpty()))
- return;
- if(mAvrcpRemoteDevice.mAbsVolNotificationState ==
- AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP) {
- int maxVol = mAudioManager.
- getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.
- getStreamVolume(AudioManager.STREAM_MUSIC);
- int percentageVol = ((currIndex*
- AvrcpControllerConstants.ABS_VOL_BASE)/maxVol);
- Log.d(TAG," Abs Vol Notify Rsp Changed vol = "+ percentageVol);
- sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
- (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_CHANGED,
- percentageVol, mAvrcpRemoteDevice.mNotificationLabel);
- }
- else if (mAvrcpRemoteDevice.mAbsVolNotificationState ==
- AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP) {
- Log.d(TAG," Don't Complete Notification Rsp. ");
- mAvrcpRemoteDevice.mAbsVolNotificationState =
- AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
- }
- }
- }
- }
- }
- };
-
- private void handlePassthroughRsp(int id, int keyState, byte[] address) {
- Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState);
- }
-
- private void onConnectionStateChanged(boolean connected, byte[] address) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- Log.d(TAG, "onConnectionStateChanged " + connected + " " + device+ " size "+
- mConnectedDevices.size());
- if (device == null)
- return;
- int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
- int newState = (connected ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
-
- if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
- /* AVRCPControllerService supports single connection */
- if(mConnectedDevices.size() > 0) {
- Log.d(TAG,"A Connection already exists, returning");
- return;
- }
- mConnectedDevices.add(device);
- Message msg = mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
- oldState, device);
- mHandler.sendMessage(msg);
- } else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
- mConnectedDevices.remove(device);
- Message msg = mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
- oldState, device);
- mHandler.sendMessage(msg);
- }
- }
-
- private void getRcFeatures(byte[] address, int features) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- Message msg = mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
- mHandler.sendMessage(msg);
- }
- private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
- /* TODO do we need to do anything here */
- }
- private void handleRegisterNotificationAbsVol(byte[] address, byte label)
- {
- Log.d(TAG,"handleRegisterNotificationAbsVol ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, label, 0);
- mHandler.sendMessage(msg);
- }
-
- private void handleSetAbsVolume(byte[] address, byte absVol, byte label)
- {
- Log.d(TAG,"handleSetAbsVolume ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- Message msg = mHandler.obtainMessage(
- AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
- mHandler.sendMessage(msg);
- }
-
- private void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
- String[] attribVals)
- {
- Log.d(TAG,"onTrackChanged ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- TrackInfo mTrack = new TrackInfo(0, numAttributes, attributes, attribVals);
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_TRACK_CHANGED, numAttributes, 0, mTrack);
- mHandler.sendMessage(msg);
- }
-
- private void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
- Log.d(TAG,"onPlayPositionChanged ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
- mHandler.sendMessage(msg);
- }
-
- private void onPlayStatusChanged(byte[] address, byte playStatus) {
- if(DBG) Log.d(TAG,"onPlayStatusChanged " + playStatus);
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playStatus, 0);
- mHandler.sendMessage(msg);
- }
-
- private void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
- Log.d(TAG,"handlePlayerAppSetting rspLen = " + rspLen);
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING, 0, 0, bb);
- mHandler.sendMessage(msg);
- }
-
- private void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
- Log.d(TAG,"onPlayerAppSettingChanged ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
- (Utils.getAddressStringFromByte(address));
- if (!mConnectedDevices.contains(device))
- return;
- ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
- Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
- MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED, 0, 0, bb);
- mHandler.sendMessage(msg);
- }
-
- private void handleGroupNavigationRsp(int id, int keyState) {
- Log.d(TAG, "group navigation response received as: key: "
- + id + " state: " + keyState);
- }
-
- private byte[] getByteAddress(BluetoothDevice device) {
- return Utils.getBytesFromAddress(device.getAddress());
- }
-
- @Override
- public void dump(StringBuilder sb) {
- super.dump(sb);
- }
-
- 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 boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
- int keyState);
- private native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
- byte[] atttibIds, byte[]attribVal);
- /* This api is used to send response to SET_ABS_VOL_CMD */
- private native void sendAbsVolRspNative(byte[] address, int absVol, int label);
- /* This api is used to inform remote for any volume level changes */
- private native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
- int label);
-}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
new file mode 100644
index 0000000..4142e63
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ */
+public class AvrcpControllerService extends ProfileService {
+ static final String TAG = "AvrcpControllerService";
+ static final boolean DBG = true;
+ static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+ /*
+ * Play State Values from JNI
+ */
+ private static final byte JNI_PLAY_STATUS_STOPPED = 0x00;
+ private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
+ private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
+ private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
+ private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
+ private static final byte JNI_PLAY_STATUS_ERROR = -1;
+
+ /*
+ * Browsing Media Item Attribute IDs
+ * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h
+ */
+ private static final int JNI_MEDIA_ATTR_ID_INVALID = -1;
+ private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001;
+ private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002;
+ private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003;
+ private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004;
+ private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005;
+ private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006;
+ private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007;
+
+ /*
+ * Browsing folder types
+ * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
+ */
+ private static final int JNI_FOLDER_TYPE_TITLES = 0x01;
+ private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02;
+ private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03;
+ private static final int JNI_FOLDER_TYPE_GENRES = 0x04;
+ private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05;
+ private static final int JNI_FOLDER_TYPE_YEARS = 0x06;
+
+ /**
+ * Intent used to broadcast the change in browse connection state of the AVRCP Controller
+ * profile.
+ *
+ * <p>This intent will have 2 extras:
+ * <ul>
+ * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+ * </ul>
+ *
+ * <p>{@link #EXTRA_STATE} can be any of
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+ * receive.
+ */
+ public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED";
+
+ /**
+ * intent used to broadcast the change in metadata state of playing track on the avrcp
+ * ag.
+ *
+ * <p>this intent will have the two extras:
+ * <ul>
+ * <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li>
+ * <li> {@link #extra_playback} - {@link playbackstate} containing the current playback
+ * state. </li>
+ * </ul>
+ */
+ public static final String ACTION_TRACK_EVENT =
+ "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+
+ /**
+ * Intent used to broadcast the change of folder list.
+ *
+ * <p>This intent will have the one extra:
+ * <ul>
+ * <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
+ * containing the folder listing of currently selected folder.
+ * </ul>
+ */
+ public static final String ACTION_FOLDER_LIST =
+ "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
+
+ public static final String EXTRA_FOLDER_LIST =
+ "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
+
+ public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
+ public static final String EXTRA_FOLDER_BT_ID =
+ "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
+
+ public static final String EXTRA_METADATA =
+ "android.bluetooth.avrcp-controller.profile.extra.METADATA";
+
+ public static final String EXTRA_PLAYBACK =
+ "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
+
+ public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
+
+ /*
+ * KeyCoded for Pass Through Commands
+ */
+ public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
+ public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
+ public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
+ public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
+ public static final int PASS_THRU_CMD_ID_STOP = 0x45;
+ public static final int PASS_THRU_CMD_ID_FF = 0x49;
+ public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
+ public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
+ public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
+
+ /* Key State Variables */
+ public static final int KEY_STATE_PRESSED = 0;
+ public static final int KEY_STATE_RELEASED = 1;
+
+ /* Group Navigation Key Codes */
+ public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
+ public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+
+ /* Folder navigation directions
+ * This is borrowed from AVRCP 1.6 spec and must be kept with same values
+ */
+ public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+ public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
+
+ /* Folder/Media Item scopes.
+ * Keep in sync with AVRCP 1.6 sec. 6.10.1
+ */
+ public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00;
+ public static final int BROWSE_SCOPE_VFS = 0x01;
+ public static final int BROWSE_SCOPE_SEARCH = 0x02;
+ public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
+
+ private AvrcpControllerStateMachine mAvrcpCtSm;
+ private static AvrcpControllerService sAvrcpControllerService;
+ // UID size is 8 bytes (AVRCP 1.6 spec)
+ private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
+
+ // We only support one device.
+ private BluetoothDevice mConnectedDevice = null;
+ // If browse is supported (only valid if mConnectedDevice != null).
+ private boolean mBrowseConnected = false;
+ // Caches the current browse folder. If this is null then root is the currently browsed folder
+ // (which also has no UID).
+ private String mCurrentBrowseFolderUID = null;
+
+ static {
+ classInitNative();
+ }
+
+ public AvrcpControllerService() {
+ initNative();
+ }
+
+ protected String getName() {
+ return TAG;
+ }
+
+ protected IProfileServiceBinder initBinder() {
+ return new BluetoothAvrcpControllerBinder(this);
+ }
+
+ protected boolean start() {
+ HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
+ thread.start();
+ mAvrcpCtSm = new AvrcpControllerStateMachine(this);
+ mAvrcpCtSm.start();
+
+ setAvrcpControllerService(this);
+ return true;
+ }
+
+ protected boolean stop() {
+ if (mAvrcpCtSm != null) {
+ mAvrcpCtSm.doQuit();
+ }
+ return true;
+ }
+
+ //API Methods
+
+ public static synchronized AvrcpControllerService getAvrcpControllerService() {
+ if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "getAvrcpControllerService(): returning "
+ + sAvrcpControllerService);
+ }
+ return sAvrcpControllerService;
+ }
+ if (DBG) {
+ if (sAvrcpControllerService == null) {
+ Log.d(TAG, "getAvrcpControllerService(): service is NULL");
+ } else if (!(sAvrcpControllerService.isAvailable())) {
+ Log.d(TAG, "getAvrcpControllerService(): service is not available");
+ }
+ }
+ return null;
+ }
+
+ private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
+ if (instance != null && instance.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
+ }
+ sAvrcpControllerService = instance;
+ } else {
+ if (DBG) {
+ if (instance == null) {
+ Log.d(TAG, "setAvrcpControllerService(): service not available");
+ } else if (!instance.isAvailable()) {
+ Log.d(TAG, "setAvrcpControllerService(): service is cleaning up");
+ }
+ }
+ }
+ }
+
+ private static synchronized void clearAvrcpControllerService() {
+ sAvrcpControllerService = null;
+ }
+
+ public synchronized List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ if (mConnectedDevice != null) {
+ devices.add(mConnectedDevice);
+ }
+ return devices;
+ }
+
+ /**
+ * This function only supports STATE_CONNECTED
+ */
+ public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ for (int i = 0; i < states.length; i++) {
+ if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) {
+ devices.add(mConnectedDevice);
+ }
+ }
+ return devices;
+ }
+
+ public synchronized int getConnectionState(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
+ if (device == null) {
+ Log.e(TAG, "sendGroupNavigationCmd device is null");
+ }
+
+ if (!(device.equals(mConnectedDevice))) {
+ Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
+ return;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_SEND_GROUP_NAVIGATION_CMD, keyCode, keyState, device);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+ if (device == null) {
+ Log.e(TAG, "sendPassThroughCmd Device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
+ return;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mAvrcpCtSm
+ .obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
+ keyCode, keyState, device);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public void startAvrcpUpdates() {
+ mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
+ }
+
+ public void stopAvrcpUpdates() {
+ mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
+ }
+
+ public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
+ if (DBG) {
+ Log.d(TAG, "getMetaData");
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (device == null) {
+ Log.e(TAG, "getMetadata device is null");
+ return null;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ return null;
+ }
+ return mAvrcpCtSm.getCurrentMetaData();
+ }
+
+ public synchronized PlaybackState getPlaybackState(BluetoothDevice device) {
+ if (DBG) {
+ Log.d(TAG, "getPlayBackState device = " + device);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getPlaybackState device is null");
+ return null;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
+ return null;
+
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAvrcpCtSm.getCurrentPlayBackState();
+ }
+
+ public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+ if (DBG) {
+ Log.d(TAG, "getPlayerApplicationSetting ");
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getPlayerSettings device is null");
+ return null;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
+ return null;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ /* Do nothing */
+ return null;
+ }
+
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+ if (DBG) {
+ Log.d(TAG, "getPlayerApplicationSetting");
+ }
+
+ /* Do nothing */
+ return false;
+ }
+
+ /**
+ * Fetches the list of children for the parentID node.
+ *
+ * This function manages the overall tree for browsing structure.
+ *
+ * Arguments:
+ * device - Device to browse content for.
+ * parentMediaId - ID of the parent that we need to browse content for. Since most
+ * of the players are database unware, fetching a root invalidates all the children.
+ * start - number of item to start scanning from
+ * items - number of items to fetch
+ */
+ public synchronized void getChildren(BluetoothDevice device, String parentMediaId, int start, int items) {
+ if (DBG) {
+ Log.d(TAG, "getChildrent device = " + device + " parent " + parentMediaId);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getChildren device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "getChildren device " + device + " does not match " +
+ mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "getChildren browse not yet connected");
+ return;
+ }
+
+ if (!mAvrcpCtSm.isConnected()) {
+ return;
+ }
+ mAvrcpCtSm.getChildren(parentMediaId, start, items);
+ }
+
+ public synchronized void getNowPlayingList(BluetoothDevice device, String id, int start, int items) {
+ if (DBG) {
+ Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start +
+ "items = " + items);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getNowPlayingList device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "getNowPlayingList browse not yet connected");
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public synchronized void getFolderList(BluetoothDevice device, String id, int start, int items) {
+ if (DBG) {
+ Log.d(TAG, "getFolderListing device = " + device + " start = " + start +
+ "items = " + items);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getFolderListing device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "getFolderListing browse not yet connected");
+ return;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public synchronized void getPlayerList(BluetoothDevice device, int start, int items) {
+ if (DBG) {
+ Log.d(TAG, "getPlayerList device = " + device + " start = " + start +
+ "items = " + items);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "getPlayerList device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "getPlayerList browse not yet connected");
+ return;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public synchronized void changeFolderPath(
+ BluetoothDevice device, int direction, String uid, String fid) {
+ if (DBG) {
+ Log.d(TAG, "changeFolderPath device = " + device + " direction " +
+ direction + " uid " + uid);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "changeFolderPath device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "changeFolderPath device " + device + " does not match " +
+ mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "changeFolderPath browse not yet connected");
+ return;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Bundle b = new Bundle();
+ b.putString(EXTRA_FOLDER_ID, fid);
+ b.putString(EXTRA_FOLDER_BT_ID, uid);
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public void setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
+ if (DBG) {
+ Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "setBrowsedPlayer device is null");
+ return;
+ }
+
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "setBrowsedPlayer browse not yet connected");
+ return;
+ }
+
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
+ if (DBG) {
+ Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
+ }
+
+ if (device == null) {
+ Log.e(TAG, "fetchAttrAndPlayItem device is null");
+ return;
+ }
+
+ if (!device.equals(mConnectedDevice)) {
+ Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " +
+ mConnectedDevice);
+ return;
+ }
+
+ if (!mBrowseConnected) {
+ Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
+ return;
+ }
+ mAvrcpCtSm.fetchAttrAndPlayItem(uid);
+ }
+
+ //Binder object: Must be static class or memory leak may occur
+ private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+ implements IProfileServiceBinder {
+
+ private AvrcpControllerService mService;
+
+ private AvrcpControllerService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "AVRCP call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+ return null;
+ }
+
+ BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
+ mService = svc;
+ }
+
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getConnectedDevices();
+ }
+
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ if (device == null) {
+ throw new IllegalStateException("Device cannot be null!");
+ }
+
+ return service.getConnectionState(device);
+ }
+
+ @Override
+ public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return;
+ }
+
+ if (device == null) {
+ throw new IllegalStateException("Device cannot be null!");
+ }
+
+ service.sendGroupNavigationCmd(device, keyCode, keyState);
+ }
+
+ @Override
+ public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+ Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return null;
+ }
+
+ if (device == null) {
+ throw new IllegalStateException("Device cannot be null!");
+ }
+
+ return service.getPlayerSettings(device);
+ }
+
+ @Override
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+ Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
+ AvrcpControllerService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setPlayerApplicationSetting(plAppSetting);
+ }
+ }
+
+ // Called by JNI when a passthrough key was received.
+ private void handlePassthroughRsp(int id, int keyState) {
+ Log.d(TAG, "passthrough response received as: key: " + id + " state: " +
+ keyState);
+ }
+
+ private void handleGroupNavigationRsp(int id, int keyState) {
+ Log.d(TAG, "group navigation response received as: key: " + id + " state: " +
+ keyState);
+ }
+
+ // Called by JNI when a device has connected or disconnected.
+ private synchronized void onConnectionStateChanged(
+ boolean rc_connected, boolean br_connected, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected +
+ device + " conn device " + mConnectedDevice);
+ if (device == null) {
+ Log.e(TAG, "onConnectionStateChanged Device is null");
+ return;
+ }
+
+ // Adjust the AVRCP connection state.
+ int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED);
+ int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+ /* AVRCPControllerService supports single connection */
+ if (mConnectedDevice != null) {
+ Log.d(TAG, "A Connection already exists, returning");
+ return;
+ }
+ mConnectedDevice = device;
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+ oldState, device);
+ mAvrcpCtSm.sendMessage(msg);
+ } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) {
+ mConnectedDevice = null;
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+ oldState, device);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Adjust the browse connection state. If RC is connected we should have already sent the
+ // connection status out.
+ if (rc_connected && br_connected) {
+ mBrowseConnected = true;
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
+ msg.arg1 = 1;
+ msg.obj = device;
+ mAvrcpCtSm.sendMessage(msg);
+ }
+ }
+
+ // Called by JNI to notify Avrcp of features supported by the Remote device.
+ private void getRcFeatures(byte[] address, int features) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI
+ private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
+ /* Do Nothing. */
+ }
+
+ // Called by JNI when remote wants to receive absolute volume notifications.
+ private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
+ Log.d(TAG, "handleRegisterNotificationAbsVol ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
+ return;
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI when remote wants to set absolute volume.
+ private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
+ Log.d(TAG, "handleSetAbsVolume ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "handleSetAbsVolume device not found " + address);
+ return;
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI when a track changes and local AvrcpController is registered for updates.
+ private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
+ String[] attribVals) {
+ if (DBG) {
+ Log.d(TAG, "onTrackChanged");
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "onTrackChanged device not found " + address);
+ return;
+ }
+
+ List<Integer> attrList = new ArrayList<>();
+ for (int attr : attributes) {
+ attrList.add(attr);
+ }
+ List<String> attrValList = Arrays.asList(attribVals);
+ TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
+ if (DBG) {
+ Log.d(TAG, "onTrackChanged " + trackInfo);
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI periodically based upon timer to update play position
+ private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
+ if (DBG) {
+ Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
+ return;
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI on changes of play status
+ private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
+ if (DBG) {
+ Log.d(TAG, "onPlayStatusChanged " + playStatus);
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
+ return;
+ }
+ int playbackState = PlaybackState.STATE_NONE;
+ switch (playStatus) {
+ case JNI_PLAY_STATUS_STOPPED:
+ playbackState = PlaybackState.STATE_STOPPED;
+ break;
+ case JNI_PLAY_STATUS_PLAYING:
+ playbackState = PlaybackState.STATE_PLAYING;
+ break;
+ case JNI_PLAY_STATUS_PAUSED:
+ playbackState = PlaybackState.STATE_PAUSED;
+ break;
+ case JNI_PLAY_STATUS_FWD_SEEK:
+ playbackState = PlaybackState.STATE_FAST_FORWARDING;
+ break;
+ case JNI_PLAY_STATUS_REV_SEEK:
+ playbackState = PlaybackState.STATE_FAST_FORWARDING;
+ break;
+ default:
+ playbackState = PlaybackState.STATE_NONE;
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // Called by JNI to report remote Player's capabilities
+ private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
+ if (DBG) {
+ Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
+ return;
+ }
+ PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.
+ makeSupportedSettings(playerAttribRsp);
+ /* Do nothing */
+ }
+
+ private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
+ if (DBG) {
+ Log.d(TAG, "onPlayerAppSettingChanged ");
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (device != null && !device.equals(mConnectedDevice)) {
+ Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
+ return;
+ }
+ PlayerApplicationSettings desiredSettings = PlayerApplicationSettings.
+ makeSettings(playerAttribRsp);
+ /* Do nothing */
+ }
+
+ // Browsing related JNI callbacks.
+ void handleGetFolderItemsRsp(MediaItem[] items) {
+ if (DBG) {
+ Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+ }
+ for (MediaItem item : items) {
+ if (DBG) {
+ Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
+ }
+ }
+ ArrayList<MediaItem> itemsList = new ArrayList<>();
+ for (MediaItem item : items) {
+ itemsList.add(item);
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
+ if (DBG) {
+ Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+ }
+ for (AvrcpPlayer item : items) {
+ if (DBG) {
+ Log.d(TAG, "bt player item: " + item);
+ }
+ }
+ List<AvrcpPlayer> itemsList = new ArrayList<>();
+ for (AvrcpPlayer p : items) {
+ itemsList.add(p);
+ }
+
+ Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+ MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ // JNI Helper functions to convert native objects to java.
+ MediaItem createFromNativeMediaItem(
+ byte[] uid, int type, String name, int[] attrIds, String[] attrVals) {
+ if (DBG) {
+ Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " +
+ name + " attrids " + attrIds + " attrVals " + attrVals);
+ }
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+
+ Bundle mdExtra = new Bundle();
+ mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+ mdb.setExtras(mdExtra);
+
+ // Generate a random UUID. We do this since database unaware TGs can send multiple
+ // items with same MEDIA_ITEM_UID_KEY.
+ mdb.setMediaId(UUID.randomUUID().toString());
+
+ // Concise readable name.
+ mdb.setTitle(name);
+
+ // We skip the attributes since we can query them using UID for the item above
+ // Also MediaDescription does not give an easy way to provide this unless we pass
+ // it as an MediaMetadata which is put inside the extras.
+ return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
+ }
+
+ MediaItem createFromNativeFolderItem(
+ byte[] uid, int type, String name, int playable) {
+ if (DBG) {
+ Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type +
+ " name " + name + " playable " + playable);
+ }
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+
+ // Covert the byte to a hex string. The coversion can be done back here to a
+ // byte array when needed.
+ Bundle mdExtra = new Bundle();
+ mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+ mdb.setExtras(mdExtra);
+
+ // Generate a random UUID. We do this since database unaware TGs can send multiple
+ // items with same MEDIA_ITEM_UID_KEY.
+ mdb.setMediaId(UUID.randomUUID().toString());
+
+ // Concise readable name.
+ mdb.setTitle(name);
+
+ return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
+ }
+
+ AvrcpPlayer createFromNativePlayerItem(
+ int id, String name, byte[] transportFlags, int playStatus, int playerType) {
+ if (DBG) {
+ Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " +
+ transportFlags + " play status " + playStatus + " player type " +
+ playerType);
+ }
+ AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
+ return player;
+ }
+
+ private void handleChangeFolderRsp(int count) {
+ if (DBG) {
+ Log.d(TAG, "handleChangeFolderRsp count: " + count);
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ private void handleSetBrowsedPlayerRsp(int items, int depth) {
+ if (DBG) {
+ Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ @Override
+ public void dump(StringBuilder sb) {
+ super.dump(sb);
+ mAvrcpCtSm.dump(sb);
+ }
+
+ public static String byteUIDToHexString(byte[] uid) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : uid) {
+ sb.append(String.format("%02X", b));
+ }
+ return sb.toString();
+ }
+
+ public static byte[] hexStringToByteUID(String uidStr) {
+ if (uidStr == null) {
+ Log.e(TAG, "Null hex string.");
+ return EMPTY_UID;
+ } else if (uidStr.length() % 2 == 1) {
+ // Odd length strings should not be possible.
+ Log.e(TAG, "Odd length hex string " + uidStr);
+ return EMPTY_UID;
+ }
+ int len = uidStr.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4)
+ + Character.digit(uidStr.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
+ private native static void classInitNative();
+
+ private native void initNative();
+
+ private native void cleanupNative();
+
+ native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+
+ native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+ int keyState);
+
+ native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+ byte[] atttibIds, byte[] attribVal);
+
+ /* This api is used to send response to SET_ABS_VOL_CMD */
+ native static void sendAbsVolRspNative(byte[] address, int absVol, int label);
+
+ /* This api is used to inform remote for any volume level changes */
+ native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+ int label);
+
+ /* API used to fetch the current now playing list */
+ native static void getNowPlayingListNative(byte[] address, byte start, byte items);
+ /* API used to fetch the current folder's listing */
+ native static void getFolderListNative(byte[] address, byte start, byte items);
+ /* API used to fetch the listing of players */
+ native static void getPlayerListNative(byte[] address, byte start, byte items);
+ /* API used to change the folder */
+ native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+ native static void playItemNative(
+ byte[] address, byte scope, byte[] uid, int uidCounter);
+ native static void setBrowsedPlayerNative(byte[] address, int playerId);
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
new file mode 100644
index 0000000..874512b
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -0,0 +1,1019 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
+ * and interactions with a remote controlable device.
+ */
+class AvrcpControllerStateMachine extends StateMachine {
+
+ // commands from Binder service
+ static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+ static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
+ static final int MESSAGE_GET_NOW_PLAYING_LIST = 5;
+ static final int MESSAGE_GET_FOLDER_LIST = 6;
+ static final int MESSAGE_GET_PLAYER_LIST = 7;
+ static final int MESSAGE_CHANGE_FOLDER_PATH = 8;
+ static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9;
+ static final int MESSAGE_SET_BROWSED_PLAYER = 10;
+
+ // commands from native layer
+ static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
+ static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
+ static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
+ static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
+ static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
+ static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108;
+ static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109;
+ static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 110;
+ static final int MESSAGE_PROCESS_FOLDER_PATH = 111;
+ static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113;
+
+ // commands from A2DP sink
+ static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
+ static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+
+ // commands for connection
+ static final int MESSAGE_PROCESS_RC_FEATURES = 301;
+ static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 302;
+ static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303;
+
+ // Interal messages
+ static final int MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT = 401;
+ static final int MESSAGE_INTERNAL_MOVE_N_LEVELS_UP = 402;
+ static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403;
+
+ static final int CMD_TIMEOUT_MILLIS = 5000; // 5s
+
+ /*
+ * Base value for absolute volume from JNI
+ */
+ private static final int ABS_VOL_BASE = 127;
+
+ /*
+ * Notification types for Avrcp protocol JNI.
+ */
+ private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
+ private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
+
+
+ private static final String TAG = "AvrcpControllerSM";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+
+ private final State mDisconnected;
+ private final State mConnected;
+ private final SetBrowsedPlayer mSetBrowsedPlayer;
+ private final ChangeFolderPath mChangeFolderPath;
+ private final GetFolderList mGetFolderListing;
+ private final GetPlayerListing mGetPlayerListing;
+ private final GetNowPlayingList mGetNowPlayingList;
+ private final MoveToRoot mMoveToRoot;
+
+ private final Object mLock = new Object();
+ private static final ArrayList<MediaItem> mEmptyMediaItemList = new ArrayList<>();
+ private static final MediaMetadata mEmptyMMD = new MediaMetadata.Builder().build();
+
+ // APIs exist to access these so they must be thread safe
+ private Boolean mIsConnected = false;
+ private RemoteDevice mRemoteDevice;
+ private AvrcpPlayer mAddressedPlayer;
+
+ // Only accessed from State Machine processMessage
+ private boolean mAbsoluteVolumeChangeInProgress = false;
+ private boolean mBroadcastMetadata = false;
+ private int previousPercentageVol = -1;
+
+ // New addressed player.
+ private String mCurrentPlayer = null;
+
+ // Depth from root of current browsing. This can be used to move to root directly.
+ // Only valid if mCurrentPlayer != null.
+ private int mBrowseDepth = 0;
+
+ // Browse tree.
+ private BrowseTree mBrowseTree = new BrowseTree();
+
+ AvrcpControllerStateMachine(Context context) {
+ super(TAG);
+ mContext = context;
+
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+
+ mDisconnected = new Disconnected();
+ mConnected = new Connected();
+
+ // Used to change folder path and fetch the new folder listing.
+ mSetBrowsedPlayer = new SetBrowsedPlayer();
+ mChangeFolderPath = new ChangeFolderPath();
+ mGetFolderListing = new GetFolderList();
+ mGetPlayerListing = new GetPlayerListing();
+ mGetNowPlayingList = new GetNowPlayingList();
+ mMoveToRoot = new MoveToRoot();
+
+ addState(mDisconnected);
+ addState(mConnected);
+
+ // Any action that needs blocking other requests to the state machine will be implemented as
+ // a separate substate of the mConnected state. Once transtition to the sub-state we should
+ // only handle the messages that are relevant to the sub-action. Everything else should be
+ // deferred so that once we transition to the mConnected we can process them hence.
+ addState(mSetBrowsedPlayer, mConnected);
+ addState(mChangeFolderPath, mConnected);
+ addState(mGetFolderListing, mConnected);
+ addState(mGetPlayerListing, mConnected);
+ addState(mGetNowPlayingList, mConnected);
+ addState(mMoveToRoot, mConnected);
+
+ setInitialState(mDisconnected);
+ }
+
+ class Disconnected extends State {
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+ switch (msg.what) {
+ case MESSAGE_PROCESS_CONNECTION_CHANGE:
+ if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
+ transitionTo(mConnected);
+ BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
+ synchronized(mLock) {
+ mRemoteDevice = new RemoteDevice(rtDevice);
+ mAddressedPlayer = new AvrcpPlayer();
+ mIsConnected = true;
+ }
+ Intent intent = new Intent(
+ BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_CONNECTED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ break;
+
+ default:
+ Log.w(TAG,"Currently Disconnected not handling " + dumpMessageString(msg.what));
+ return false;
+ }
+ return true;
+ }
+ }
+
+ class Connected extends State {
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ synchronized (mLock) {
+ switch (msg.what) {
+ case MESSAGE_STOP_METADATA_BROADCASTS:
+ mBroadcastMetadata = false;
+ broadcastPlayBackStateChanged(new PlaybackState.Builder().setState(
+ PlaybackState.STATE_PAUSED, mAddressedPlayer.getPlayTime(),
+ 0).build());
+ break;
+
+ case MESSAGE_START_METADATA_BROADCASTS:
+ mBroadcastMetadata = true;
+ broadcastPlayBackStateChanged(mAddressedPlayer.getPlaybackState());
+ if (mAddressedPlayer.getCurrentTrack() != null) {
+ broadcastMetaDataChanged(
+ mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+ }
+ break;
+
+ case MESSAGE_SEND_PASS_THROUGH_CMD:
+ BluetoothDevice device = (BluetoothDevice) msg.obj;
+ AvrcpControllerService
+ .sendPassThroughCommandNative(Utils.getByteAddress(device), msg.arg1,
+ msg.arg2);
+ if (a2dpSinkService != null) {
+ Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
+ a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
+ }
+ break;
+
+ case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+ AvrcpControllerService.sendGroupNavigationCommandNative(
+ mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
+ break;
+
+ case MESSAGE_GET_NOW_PLAYING_LIST:
+ AvrcpControllerService.getNowPlayingListNative(
+ mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+ (byte) msg.arg2);
+ mGetNowPlayingList.setFolder((String) msg.obj);
+ transitionTo(mGetNowPlayingList);
+ break;
+
+ case MESSAGE_GET_FOLDER_LIST:
+ AvrcpControllerService.getFolderListNative(
+ mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+ (byte) msg.arg2);
+
+ // Whenever we transition we set the information for folder we need to
+ // return result.
+ mGetFolderListing.setFolder((String) msg.obj);
+ transitionTo(mGetFolderListing);
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ break;
+
+ case MESSAGE_GET_PLAYER_LIST:
+ AvrcpControllerService.getPlayerListNative(
+ mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+ (byte) msg.arg2);
+ transitionTo(mGetPlayerListing);
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ break;
+
+ case MESSAGE_CHANGE_FOLDER_PATH: {
+ int direction = msg.arg1;
+ Bundle b = (Bundle) msg.obj;
+ String uid = b.getString(AvrcpControllerService.EXTRA_FOLDER_BT_ID);
+ String fid = b.getString(AvrcpControllerService.EXTRA_FOLDER_ID);
+
+ // String is encoded as a Hex String (mostly for display purposes)
+ // hence convert this back to real byte string.
+ AvrcpControllerService.changeFolderPathNative(
+ mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+ AvrcpControllerService.hexStringToByteUID(uid));
+ mChangeFolderPath.setFolder(fid);
+ transitionTo(mChangeFolderPath);
+ sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1);
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ break;
+ }
+
+ case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
+ int scope = msg.arg1;
+ String uid = (String) msg.obj;
+ // String is encoded as a Hex String (mostly for display purposes)
+ // hence convert this back to real byte string.
+ AvrcpControllerService.playItemNative(
+ mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+ AvrcpControllerService.hexStringToByteUID(uid), (int) 0);
+ break;
+ }
+
+ case MESSAGE_SET_BROWSED_PLAYER: {
+ AvrcpControllerService.setBrowsedPlayerNative(
+ mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
+ mSetBrowsedPlayer.setFolder((String) msg.obj);
+ transitionTo(mSetBrowsedPlayer);
+ break;
+ }
+
+ case MESSAGE_PROCESS_CONNECTION_CHANGE:
+ if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
+ synchronized (mLock) {
+ mIsConnected = false;
+ mRemoteDevice = null;
+ }
+ transitionTo(mDisconnected);
+ BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
+ Intent intent = new Intent(
+ BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+ BluetoothProfile.STATE_CONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ break;
+
+ case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
+ // Service tells us if the browse is connected or disconnected.
+ // This is useful only for deciding whether to send browse commands rest of
+ // the connection state handling should be done via the message
+ // MESSAGE_PROCESS_CONNECTION_CHANGE.
+ Intent intent = new Intent(
+ AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) msg.obj);
+ if (DBG) {
+ Log.d(TAG, "Browse connection state " + msg.arg1);
+ }
+ if (msg.arg1 == 1) {
+ intent.putExtra(
+ BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+ } else if (msg.arg1 == 0) {
+ intent.putExtra(
+ BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+ // If browse is disconnected, the next time we connect we should
+ // be at the ROOT.
+ mBrowseDepth = 0;
+ } else {
+ Log.w(TAG, "Incorrect browse state " + msg.arg1);
+ }
+
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+
+ case MESSAGE_PROCESS_RC_FEATURES:
+ mRemoteDevice.setRemoteFeatures(msg.arg1);
+ break;
+
+ case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ mAbsoluteVolumeChangeInProgress = true;
+ setAbsVolume(msg.arg1, msg.arg2);
+ break;
+
+ case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
+ mRemoteDevice.setNotificationLabel(msg.arg1);
+ mRemoteDevice.setAbsVolNotificationRequested(true);
+ int percentageVol = getVolumePercentage();
+ Log.d(TAG,
+ " Sending Interim Response = " + percentageVol + " label " + msg.arg1);
+ AvrcpControllerService
+ .sendRegisterAbsVolRspNative(mRemoteDevice.getBluetoothAddress(),
+ NOTIFICATION_RSP_TYPE_INTERIM,
+ percentageVol,
+ mRemoteDevice.getNotificationLabel());
+ }
+ break;
+
+ case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
+ if (mAbsoluteVolumeChangeInProgress) {
+ mAbsoluteVolumeChangeInProgress = false;
+ } else {
+ if (mRemoteDevice.getAbsVolNotificationRequested()) {
+ int percentageVol = getVolumePercentage();
+ if (percentageVol != previousPercentageVol) {
+ AvrcpControllerService.sendRegisterAbsVolRspNative(
+ mRemoteDevice.getBluetoothAddress(),
+ NOTIFICATION_RSP_TYPE_CHANGED,
+ percentageVol, mRemoteDevice.getNotificationLabel());
+ previousPercentageVol = percentageVol;
+ mRemoteDevice.setAbsVolNotificationRequested(false);
+ }
+ }
+ }
+ }
+ break;
+
+ case MESSAGE_PROCESS_TRACK_CHANGED:
+ mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
+ if (mBroadcastMetadata) {
+ broadcastMetaDataChanged(mAddressedPlayer.getCurrentTrack().
+ getMediaMetaData());
+ }
+ break;
+
+ case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ mAddressedPlayer.setPlayTime(msg.arg2);
+ if (mBroadcastMetadata) {
+ broadcastPlayBackStateChanged(getCurrentPlayBackState());
+ }
+ break;
+
+ case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+ int status = msg.arg1;
+ mAddressedPlayer.setPlayStatus(status);
+ if (status == PlaybackState.STATE_PLAYING) {
+ a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, true);
+ } else if (status == PlaybackState.STATE_PAUSED ||
+ status == PlaybackState.STATE_STOPPED) {
+ a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false);
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ // Handle the change folder path meta-action.
+ // a) Send Change folder command
+ // b) Once successful transition to folder fetch state.
+ class ChangeFolderPath extends CmdState {
+ private String STATE_TAG = "AVRCPSM.ChangeFolderPath";
+ private int mTmpIncrDirection;
+ private String mID = "";
+
+ public void setFolder(String id) {
+ mID = id;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ mTmpIncrDirection = -1;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg);
+ switch (msg.what) {
+ case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT:
+ mTmpIncrDirection = msg.arg1;
+ break;
+
+ case MESSAGE_PROCESS_FOLDER_PATH: {
+ // Fetch the listing of objects in this folder.
+ Log.d(STATE_TAG, "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 +
+ " elements");
+
+ // Update the folder depth.
+ if (mTmpIncrDirection ==
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP) {
+ mBrowseDepth -= 1;;
+ } else if (mTmpIncrDirection ==
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN) {
+ mBrowseDepth += 1;
+ } else {
+ throw new IllegalStateException("incorrect nav " + mTmpIncrDirection);
+ }
+ Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+
+ if (msg.arg1 > 0) {
+ sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
+ } else {
+ // Return an empty response to the upper layer.
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ }
+ mBrowseTree.setCurrentBrowsedFolder(mID);
+ transitionTo(mConnected);
+ break;
+ }
+
+ case MESSAGE_INTERNAL_CMD_TIMEOUT:
+ // We timed out changing folders. It is imperative we tell
+ // the upper layers that we failed by giving them an empty list.
+ Log.e(STATE_TAG, "change folder failed, sending empty list.");
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to Connected state.");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ // Handle the get folder listing action
+ // a) Fetch the listing of folders
+ // b) Once completed return the object listing
+ class GetFolderList extends CmdState {
+ private String STATE_TAG = "AVRCPSM.GetFolderList";
+
+ String mID = "";
+
+ public void setFolder(String id) {
+ Log.d(STATE_TAG, "Setting folder to " + id);
+ mID = id;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg);
+ switch (msg.what) {
+ case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
+ ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
+ BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
+ if (bn.isPlayer()) {
+ // Add the now playing folder.
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" +
+ bn.getPlayerID());
+ mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX);
+ Bundle mdBundle = new Bundle();
+ mdBundle.putString(
+ AvrcpControllerService.MEDIA_ITEM_UID_KEY,
+ BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID());
+ mdb.setExtras(mdBundle);
+ folderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
+ }
+
+ mBrowseTree.refreshChildren(bn, folderList);
+ broadcastFolderList(mID, folderList);
+
+ transitionTo(mConnected);
+ break;
+
+ case MESSAGE_INTERNAL_CMD_TIMEOUT:
+ // We have timed out to execute the request.
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ // Handle the get player listing action
+ // a) Fetch the listing of players
+ // b) Once completed return the object listing
+ class GetPlayerListing extends CmdState {
+ private String STATE_TAG = "AVRCPSM.GetPlayerList";
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg);
+ switch (msg.what) {
+ case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+ List<AvrcpPlayer> playerList =
+ (List<AvrcpPlayer>) msg.obj;
+ mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
+ ArrayList<MediaItem> mediaItemList = new ArrayList<>();
+ for (BrowseTree.BrowseNode c :
+ mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT).getChildren()) {
+ mediaItemList.add(c.getMediaItem());
+ }
+ broadcastFolderList(BrowseTree.ROOT, mediaItemList);
+ mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+ transitionTo(mConnected);
+ break;
+
+ case MESSAGE_INTERNAL_CMD_TIMEOUT:
+ // We have timed out to execute the request.
+ // Send an empty list here.
+ broadcastFolderList(BrowseTree.ROOT, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ class GetNowPlayingList extends CmdState {
+ private String STATE_TAG = "AVRCPSM.NowPlayingList";
+ private String mID = "";
+
+ public void setFolder(String id) {
+ mID = id;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg);
+ switch (msg.what) {
+ case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
+ ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
+ broadcastFolderList(mID, folderList);
+
+ BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
+ mBrowseTree.refreshChildren(bn, folderList);
+ mBrowseTree.setCurrentBrowsedFolder(mID);
+ transitionTo(mConnected);
+ break;
+
+ case MESSAGE_INTERNAL_CMD_TIMEOUT:
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ class MoveToRoot extends CmdState {
+ private String STATE_TAG = "AVRCPSM.MoveToRoot";
+ private String mID = "";
+
+ public void setFolder(String id) {
+ Log.d(STATE_TAG, "setFolder " + id);
+ mID = id;
+ }
+
+ @Override
+ public void enter() {
+ // Setup the timeouts.
+ super.enter();
+
+ // We need to move mBrowseDepth levels up. The following message is
+ // completely internal to this state.
+ sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg + " browse depth " + mBrowseDepth);
+ switch (msg.what) {
+ case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP:
+ if (mBrowseDepth == 0) {
+ Log.w(STATE_TAG, "Already in root!");
+ transitionTo(mConnected);
+ sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
+ } else {
+ AvrcpControllerService.changeFolderPathNative(
+ mRemoteDevice.getBluetoothAddress(),
+ (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+ AvrcpControllerService.hexStringToByteUID(null));
+ }
+ break;
+
+ case MESSAGE_PROCESS_FOLDER_PATH:
+ mBrowseDepth -= 1;
+ Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+ if (mBrowseDepth < 0) {
+ throw new IllegalArgumentException("Browse depth negative!");
+ }
+
+ sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ class SetBrowsedPlayer extends CmdState {
+ private String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
+ String mID = "";
+
+ public void setFolder(String id) {
+ mID = id;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(STATE_TAG, "processMessage " + msg);
+ switch (msg.what) {
+ case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
+ // Set the new depth.
+ Log.d(STATE_TAG, "player depth " + msg.arg2);
+ mBrowseDepth = msg.arg2;
+
+ // If we already on top of player and there is no content.
+ // This should very rarely happen.
+ if (mBrowseDepth == 0 && msg.arg1 == 0) {
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ } else {
+ // Otherwise move to root and fetch the listing.
+ // the MoveToRoot#enter() function takes care of fetch.
+ mMoveToRoot.setFolder(mID);
+ transitionTo(mMoveToRoot);
+ }
+ mBrowseTree.setCurrentBrowsedFolder(mID);
+ break;
+
+ case MESSAGE_INTERNAL_CMD_TIMEOUT:
+ broadcastFolderList(mID, mEmptyMediaItemList);
+ transitionTo(mConnected);
+ break;
+
+ default:
+ Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ // Class template for commands. Each state should do the following:
+ // (a) In enter() send a timeout message which could be tracked in the
+ // processMessage() stage.
+ // (b) In exit() remove all the timeouts.
+ //
+ // Essentially the lifecycle of a timeout should be bounded to a CmdState always.
+ abstract class CmdState extends State {
+ @Override
+ public void enter() {
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ }
+
+ @Override
+ public void exit() {
+ removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ }
+ }
+
+ // Interface APIs
+ boolean isConnected() {
+ synchronized (mLock) {
+ return mIsConnected;
+ }
+ }
+
+ void doQuit() {
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException expected) {
+ // If the receiver was never registered unregister will throw an
+ // IllegalArgumentException.
+ }
+ quit();
+ }
+
+ void dump(StringBuilder sb) {
+ ProfileService.println(sb, "StateMachine: " + this.toString());
+ }
+
+ MediaMetadata getCurrentMetaData() {
+ synchronized (mLock) {
+ if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) {
+ MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData();
+ if (DBG) {
+ Log.d(TAG, "getCurrentMetaData mmd " + mmd);
+ }
+ }
+ return mEmptyMMD;
+ }
+ }
+
+ PlaybackState getCurrentPlayBackState() {
+ synchronized (mLock) {
+ if (mAddressedPlayer == null) {
+ return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN,0).build();
+ }
+ return mAddressedPlayer.getPlaybackState();
+ }
+ }
+
+ // Browsing related functions.
+ void getChildren(String parentMediaId, int start, int items) {
+ BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
+ if (bn == null) {
+ Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
+ broadcastFolderList(parentMediaId, mEmptyMediaItemList);
+ return;
+ }
+
+ Message msg = null;
+ if (BrowseTree.ROOT.equals(parentMediaId)) {
+ // Root contains the list of players.
+ msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
+ } else if (bn.isPlayer()) {
+ // Set browsed (and addressed player) as the new player.
+ // This should fetch the list of folders.
+ msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER,
+ bn.getPlayerID(), 0, bn.getID());
+ } else if (bn.isNowPlaying()) {
+ // Get all songs in the list.
+ msg = obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
+ start, items, parentMediaId);
+ } else {
+ // Only change folder if desired. If an app refreshes a folder
+ // (because it resumed etc) and current folder does not change
+ // then we can simply fetch list.
+ BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
+
+ // We exempt two conditions from change folder:
+ // a) If the new folder is the same as current folder (refresh of UI)
+ // b) If the new folder is ROOT and current folder is NOW_PLAYING. In this
+ // condition we 'fake' child-parent hierarchy but it does not exist in
+ // bluetooth world.
+ boolean isNowPlayingToRoot =
+ currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
+ if (!bn.equals(currFol) && !isNowPlayingToRoot) {
+ // Find the direction of traversal.
+ int direction;
+ int btDirection = mBrowseTree.getDirection(parentMediaId);
+ Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
+ if (btDirection == BrowseTree.DIRECTION_DOWN) {
+ direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
+ } else if (btDirection == BrowseTree.DIRECTION_UP) {
+ direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
+ } else {
+ Log.w(TAG, "parent " + bn + " is not a direct " +
+ "successor or predeccessor of current folder " + currFol);
+ broadcastFolderList(parentMediaId, mEmptyMediaItemList);
+ return;
+ }
+ Bundle b = new Bundle();
+ b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID());
+ b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID());
+ msg = obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
+ } else {
+ // Fetch the listing without changing paths.
+ msg = obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST,
+ start, items, bn.getFolderUID());
+ }
+ }
+ if (msg != null) {
+ sendMessage(msg);
+ }
+ }
+
+ public void fetchAttrAndPlayItem(String uid) {
+ BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
+ BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
+ Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
+ if (currItem != null) {
+ int scope = currFolder.isNowPlaying() ?
+ AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING :
+ AvrcpControllerService.BROWSE_SCOPE_VFS;
+ Message msg = obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
+ scope, 0, currItem.getFolderUID());
+ sendMessage(msg);
+ }
+ }
+
+ private void broadcastMetaDataChanged(MediaMetadata metadata) {
+ Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
+ intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
+ if (DBG) {
+ Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
+ }
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
+ Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
+ Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
+ intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
+ intent.putParcelableArrayListExtra(
+ AvrcpControllerService.EXTRA_FOLDER_LIST, items);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private void broadcastPlayBackStateChanged(PlaybackState state) {
+ Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
+ intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
+ if (DBG) {
+ Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
+ }
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private void setAbsVolume(int absVol, int label) {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ // Ignore first volume command since phone may not know difference between stream volume
+ // and amplifier volume.
+ if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
+ 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) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+ AudioManager.FLAG_SHOW_UI);
+ }
+ } else {
+ mRemoteDevice.setFirstAbsVolCmdRecvd();
+ absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
+ Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
+ }
+ AvrcpControllerService.sendAbsVolRspNative(
+ mRemoteDevice.getBluetoothAddress(), absVol, label);
+ }
+
+ private int getVolumePercentage() {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
+ return percentageVol;
+ }
+
+ 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) {
+ sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION);
+ }
+ }
+ }
+ };
+
+ public static String dumpMessageString(int message) {
+ String str = "UNKNOWN";
+ switch (message) {
+ case MESSAGE_SEND_PASS_THROUGH_CMD:
+ str = "REQ_PASS_THROUGH_CMD";
+ break;
+ case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+ str = "REQ_GRP_NAV_CMD";
+ break;
+ case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ str = "CB_SET_ABS_VOL_CMD";
+ break;
+ case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+ str = "CB_REGISTER_ABS_VOL";
+ break;
+ case MESSAGE_PROCESS_TRACK_CHANGED:
+ str = "CB_TRACK_CHANGED";
+ break;
+ case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ str = "CB_PLAY_POS_CHANGED";
+ break;
+ case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+ str = "CB_PLAY_STATUS_CHANGED";
+ break;
+ case MESSAGE_PROCESS_RC_FEATURES:
+ str = "CB_RC_FEATURES";
+ break;
+ case MESSAGE_PROCESS_CONNECTION_CHANGE:
+ str = "CB_CONN_CHANGED";
+ break;
+ default:
+ str = Integer.toString(message);
+ break;
+ }
+ return str;
+ }
+
+ public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
+ StringBuffer sb = new StringBuffer();
+ int supportedSetting = mSett.getSettings();
+ if(VDBG) Log.d(TAG," setting: " + supportedSetting);
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+ sb.append(" EQ : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_EQUALIZER)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+ sb.append(" REPEAT : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_REPEAT)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+ sb.append(" SHUFFLE : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_SHUFFLE)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+ sb.append(" SCAN : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_SCAN)));
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
new file mode 100644
index 0000000..3f037c2
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.media.session.PlaybackState;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/*
+ * Contains information about remote player
+ */
+class AvrcpPlayer {
+ private static final String TAG = "AvrcpPlayer";
+ private static final boolean DBG = true;
+
+ public static final int INVALID_ID = -1;
+
+ private int mPlayStatus = PlaybackState.STATE_NONE;
+ private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ private int mId;
+ private String mName = "";
+ private int mPlayerType;
+ private TrackInfo mCurrentTrack = new TrackInfo();
+
+ AvrcpPlayer() {
+ mId = INVALID_ID;
+ }
+
+ AvrcpPlayer(int id, String name, int transportFlags, int playStatus, int playerType) {
+ mId = id;
+ mName = name;
+ mPlayerType = playerType;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setPlayTime(int playTime) {
+ mPlayTime = playTime;
+ }
+
+ public long getPlayTime() {
+ return mPlayTime;
+ }
+
+ public void setPlayStatus(int playStatus) {
+ mPlayStatus = playStatus;
+ }
+
+ public PlaybackState getPlaybackState() {
+ if (DBG) {
+ Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
+ }
+
+ long position = mPlayTime;
+ float speed = 1;
+ switch (mPlayStatus) {
+ case PlaybackState.STATE_STOPPED:
+ position = 0;
+ speed = 0;
+ break;
+ case PlaybackState.STATE_PAUSED:
+ speed = 0;
+ break;
+ case PlaybackState.STATE_FAST_FORWARDING:
+ speed = 3;
+ break;
+ case PlaybackState.STATE_REWINDING:
+ speed = -3;
+ break;
+ }
+ return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
+ }
+
+ synchronized public void updateCurrentTrack(TrackInfo update) {
+ mCurrentTrack = update;
+ }
+
+ synchronized public TrackInfo getCurrentTrack() {
+ return mCurrentTrack;
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
new file mode 100644
index 0000000..a11988d
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService.Result;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+// Browsing hierarchy.
+// Root:
+// Player1:
+// Now_Playing:
+// MediaItem1
+// MediaItem2
+// Folder1
+// Folder2
+// ....
+// Player2
+// ....
+public class BrowseTree {
+ private static final String TAG = "BrowseTree";
+ private static final boolean DBG = true;
+
+ public static final int DIRECTION_DOWN = 0;
+ public static final int DIRECTION_UP = 1;
+ public static final int DIRECTION_UNKNOWN = -1;
+
+ public static final String ROOT = "__ROOT__";
+ public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
+ public static final String PLAYER_PREFIX = "PLAYER";
+
+ // Static instance of Folder ID <-> Folder Instance (for navigation purposes)
+ private final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
+ private BrowseNode mCurrentBrowseNode;
+
+ BrowseTree() {
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ mdb.setMediaId(ROOT);
+ mdb.setTitle(ROOT);
+ Bundle mdBundle = new Bundle();
+ mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
+ mdb.setExtras(mdBundle);
+ mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
+ mCurrentBrowseNode = mBrowseMap.get(ROOT);
+ }
+
+ // Each node of the tree is represented by Folder ID, Folder Name and the children.
+ class BrowseNode {
+ // MediaItem to store the media related details.
+ MediaItem mItem;
+
+ // Type of this browse node.
+ // Since Media APIs do not define the player separately we define that
+ // distinction here.
+ boolean mIsPlayer = false;
+
+ // Result object if this node is not loaded yet. This result object will be used
+ // once loading is finished.
+ Result<List<MediaItem>> mResult = null;
+
+ // List of children.
+ final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+
+ BrowseNode(MediaItem item) {
+ mItem = item;
+ }
+
+ BrowseNode(AvrcpPlayer player) {
+ mIsPlayer = true;
+
+ // Transform the player into a item.
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ Bundle mdExtra = new Bundle();
+ String playerKey = PLAYER_PREFIX + player.getId();
+ mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
+ mdb.setExtras(mdExtra);
+ mdb.setMediaId(playerKey);
+ mdb.setTitle(player.getName());
+ mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
+ }
+
+ synchronized List<BrowseNode> getChildren() {
+ return mChildren;
+ }
+
+ synchronized boolean isChild(BrowseNode node) {
+ for (BrowseNode bn : mChildren) {
+ if (bn.equals(node)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Fetch the Unique UID for this item, this is unique across all elements in the tree.
+ synchronized String getID() {
+ return mItem.getDescription().getMediaId();
+ }
+
+ // Get the BT Player ID associated with this node.
+ synchronized int getPlayerID() {
+ return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
+ }
+
+ // Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
+ // This may not be unique hence this combined with direction will define the
+ // browsing here.
+ synchronized String getFolderUID() {
+ return mItem.getDescription().getExtras().getString(
+ AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+ }
+
+ synchronized MediaItem getMediaItem() {
+ return mItem;
+ }
+
+ synchronized boolean isPlayer() {
+ return mIsPlayer;
+ }
+
+ synchronized boolean isNowPlaying() {
+ return getID().startsWith(NOW_PLAYING_PREFIX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof BrowseNode)) {
+ return false;
+ }
+ BrowseNode otherNode = (BrowseNode) other;
+ return getID().equals(otherNode.getID());
+ }
+
+ @Override
+ public String toString() {
+ return "ID: " + getID() + " desc: " + mItem;
+ }
+ }
+
+ synchronized <E> void refreshChildren(String parentID, List<E> children) {
+ BrowseNode parent = findFolderByIDLocked(parentID);
+ if (parent == null) {
+ Log.w(TAG, "parent not found for parentID " + parentID);
+ return;
+ }
+ refreshChildren(parent, children);
+ }
+
+ synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
+ if (children == null) {
+ Log.e(TAG, "children cannot be null ");
+ return;
+ }
+
+ List<BrowseNode> bnList = new ArrayList<BrowseNode>();
+ for (E child : children) {
+ if (child instanceof MediaItem) {
+ bnList.add(new BrowseNode((MediaItem) child));
+ } else if (child instanceof AvrcpPlayer) {
+ bnList.add(new BrowseNode((AvrcpPlayer) child));
+ }
+ }
+
+ String parentID = parent.getID();
+ // Make sure that the child list is clean.
+ if (DBG) {
+ Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
+ }
+
+ addChildrenLocked(parent, bnList);
+ List<MediaItem> childrenList = new ArrayList<MediaItem>();
+ for (BrowseNode bn : parent.getChildren()) {
+ childrenList.add(bn.getMediaItem());
+ }
+ }
+
+ synchronized BrowseNode findBrowseNodeByID(String parentID) {
+ BrowseNode bn = mBrowseMap.get(parentID);
+ if (bn == null) {
+ Log.e(TAG, "folder " + parentID + " not found!");
+ return null;
+ }
+ if (DBG) {
+ Log.d(TAG, "Browse map: " + mBrowseMap);
+ }
+ return bn;
+ }
+
+ BrowseNode findFolderByIDLocked(String parentID) {
+ return mBrowseMap.get(parentID);
+ }
+
+ void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
+ // Remove existing children and then add the new children.
+ for (BrowseNode c : parent.getChildren()) {
+ mBrowseMap.remove(c.getID());
+ }
+ parent.getChildren().clear();
+
+ for (BrowseNode bn : items) {
+ parent.getChildren().add(bn);
+ mBrowseMap.put(bn.getID(), bn);
+ }
+ }
+
+ synchronized int getDirection(String toUID) {
+ BrowseNode fromFolder = mCurrentBrowseNode;
+ BrowseNode toFolder = findFolderByIDLocked(toUID);
+ if (fromFolder == null || toFolder == null) {
+ Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
+ }
+
+ // Check the relationship.
+ if (fromFolder.isChild(toFolder)) {
+ return DIRECTION_DOWN;
+ } else if (toFolder.isChild(fromFolder)) {
+ return DIRECTION_UP;
+ } else {
+ Log.w(TAG, "from folder " + mCurrentBrowseNode + " children " +
+ fromFolder.getChildren() + "to folder " + toUID + " children " +
+ toFolder.getChildren());
+ return DIRECTION_UNKNOWN;
+ }
+ }
+
+ synchronized boolean setCurrentBrowsedFolder(String uid) {
+ BrowseNode bn = findFolderByIDLocked(uid);
+ if (bn == null) {
+ Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
+ return false;
+ }
+ mCurrentBrowseNode = bn;
+ return true;
+ }
+
+ synchronized BrowseNode getCurrentBrowsedFolder() {
+ return mCurrentBrowseNode;
+ }
+
+ @Override
+ public String toString() {
+ return mBrowseMap.toString();
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
new file mode 100644
index 0000000..65d9966
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings
+ */
+class PlayerApplicationSettings {
+ private static final String TAG = "PlayerApplicationSettings";
+
+ /*
+ * Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F.
+ */
+ private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01;
+ private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02;
+ private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03;
+ private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04;
+
+ private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
+ private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
+
+ private static final byte JNI_REPEAT_STATUS_OFF = 0x01;
+ private static final byte JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+ private static final byte JNI_REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+ private static final byte JNI_REPEAT_STATUS_GROUP_REPEAT = 0x04;
+
+ private static final byte JNI_SHUFFLE_STATUS_OFF = 0x01;
+ private static final byte JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+ private static final byte JNI_SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+
+ private static final byte JNI_SCAN_STATUS_OFF = 0x01;
+ private static final byte JNI_SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
+ private static final byte JNI_SCAN_STATUS_GROUP_SCAN = 0x03;
+
+ private static final byte JNI_STATUS_INVALID = -1;
+
+
+ /*
+ * Hash map of current settings.
+ */
+ private Map<Integer, Integer> mSettings = new HashMap<Integer, Integer>();
+
+ /*
+ * Hash map of supported values, a setting should be supported by the remote in order to enable
+ * in mSettings.
+ */
+ private Map<Integer, ArrayList<Integer>> mSupportedValues =
+ new HashMap<Integer, ArrayList<Integer>>();
+
+ /* Convert from JNI array to Java classes. */
+ static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
+ PlayerApplicationSettings newObj = new PlayerApplicationSettings();
+ try {
+ for (int i = 0; i < btAvrcpAttributeList.length; ) {
+ byte attrId = btAvrcpAttributeList[i++];
+ byte numSupportedVals = btAvrcpAttributeList[i++];
+ ArrayList<Integer> supportedValues = new ArrayList<Integer>();
+
+ for (int j = 0; j < numSupportedVals; j++) {
+ // Yes, keep using i for array indexing.
+ supportedValues.add(mapAttribIdValtoAvrcpPlayerSetting(attrId,
+ btAvrcpAttributeList[i++]));
+ }
+ newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+ supportedValues);
+ }
+ } catch (ArrayIndexOutOfBoundsException exception) {
+ Log.e(TAG,"makeSupportedSettings attributeList index error.");
+ }
+ return newObj;
+ }
+
+ public BluetoothAvrcpPlayerSettings getAvrcpSettings() {
+ int supportedSettings = 0;
+ for (Integer setting : mSettings.keySet()) {
+ supportedSettings |= setting;
+ }
+ BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings);
+ for (Integer setting : mSettings.keySet()) {
+ result.addSettingValue(setting, mSettings.get(setting));
+ }
+ return result;
+ }
+
+ static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) {
+ PlayerApplicationSettings newObj = new PlayerApplicationSettings();
+ try {
+ for (int i = 0; i < btAvrcpAttributeList.length; ) {
+ byte attrId = btAvrcpAttributeList[i++];
+
+ newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+ mapAttribIdValtoAvrcpPlayerSetting(attrId,
+ btAvrcpAttributeList[i++]));
+ }
+ } catch (ArrayIndexOutOfBoundsException exception) {
+ Log.e(TAG,"makeSettings JNI_ATTRIButeList index error.");
+ }
+ return newObj;
+ }
+
+ public void setSupport(PlayerApplicationSettings updates) {
+ mSettings = updates.mSettings;
+ mSupportedValues = updates.mSupportedValues;
+ }
+
+ public void setValues(BluetoothAvrcpPlayerSettings updates) {
+ int supportedSettings = updates.getSettings();
+ for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) {
+ if ((i & supportedSettings) > 0) {
+ mSettings.put(i, updates.getSettingValue(i));
+ }
+ }
+ }
+
+ /*
+ * Check through all settings to ensure that they are all available to be set and then check
+ * that the desired value is in fact supported by our remote player.
+ */
+ public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) {
+ int settingSubset = settingsToCheck.getSettings();
+ int supportedSettings = 0;
+ for (Integer setting : mSupportedValues.keySet()) {
+ supportedSettings |= setting;
+ }
+ try {
+ if ((supportedSettings & settingSubset) == settingSubset) {
+ for (Integer settingId : mSettings.keySet()) {
+ if ((settingId & settingSubset )== settingId &&
+ (!mSupportedValues.get(settingId).contains(settingsToCheck.
+ getSettingValue(settingId))))
+ // The setting is in both settings to check and supported settings but the
+ // value is not supported.
+ return false;
+ }
+ return true;
+ }
+ } catch (NullPointerException e) {
+ Log.e(TAG,
+ "supportsSettings received a supported setting that has no supported values.");
+ }
+ return false;
+ }
+
+ // Convert currently desired settings into an attribute array to pass to the native layer to
+ // enable them.
+ public ArrayList<Byte> getNativeSettings() {
+ int i = 0;
+ ArrayList<Byte> attribArray = new ArrayList<Byte>();
+ for (Integer settingId : mSettings.keySet()) {
+ switch (settingId)
+ {
+ case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
+ attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
+ attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+ settingId, mSettings.get(settingId)));
+ break;
+ case BluetoothAvrcpPlayerSettings.SETTING_REPEAT:
+ attribArray.add(JNI_ATTRIB_REPEAT_STATUS);
+ attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+ settingId, mSettings.get(settingId)));
+ break;
+ case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE:
+ attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS);
+ attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+ settingId, mSettings.get(settingId)));
+ break;
+ case BluetoothAvrcpPlayerSettings.SETTING_SCAN:
+ attribArray.add(JNI_ATTRIB_SCAN_STATUS);
+ attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+ settingId, mSettings.get(settingId)));
+ break;
+ default:
+ Log.w(TAG,"Unknown setting found in getNativeSettings: " + settingId);
+ }
+ }
+ return attribArray;
+ }
+
+ // Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
+ private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
+ if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) {
+ switch(attribVal) {
+ case JNI_EQUALIZER_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ case JNI_EQUALIZER_STATUS_ON:
+ return BluetoothAvrcpPlayerSettings.STATE_ON;
+ }
+ } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) {
+ switch(attribVal) {
+ case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case JNI_REPEAT_STATUS_GROUP_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case JNI_REPEAT_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+ }
+ } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
+ switch(attribVal) {
+ case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case JNI_SCAN_STATUS_GROUP_SCAN:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case JNI_SCAN_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ }
+ } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
+ switch(attribVal) {
+ case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case JNI_SHUFFLE_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ }
+ }
+ return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+ }
+
+ // Convert an AVRCP Setting/Value pair into the native equivalent value;
+ private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
+ if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return JNI_EQUALIZER_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ON:
+ return JNI_EQUALIZER_STATUS_ON;
+ }
+ } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return JNI_REPEAT_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
+ return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return JNI_REPEAT_STATUS_GROUP_REPEAT;
+ }
+ } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return JNI_SHUFFLE_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
+ }
+ } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return JNI_SCAN_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return JNI_SCAN_STATUS_ALL_TRACK_SCAN;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return JNI_SCAN_STATUS_GROUP_SCAN;
+ }
+ }
+ return JNI_STATUS_INVALID;
+ }
+
+ // convert a native Attribute Id into the AVRCP Setting equivalent value;
+ private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
+ switch(attribId) {
+ case JNI_ATTRIB_EQUALIZER_STATUS:
+ return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
+ case JNI_ATTRIB_REPEAT_STATUS:
+ return BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
+ case JNI_ATTRIB_SHUFFLE_STATUS:
+ return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
+ case JNI_ATTRIB_SCAN_STATUS:
+ return BluetoothAvrcpPlayerSettings.SETTING_SCAN;
+ default:
+ return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+ }
+ }
+
+}
+
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
new file mode 100644
index 0000000..e535f92
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.media.session.PlaybackState;
+
+import com.android.bluetooth.Utils;
+
+import java.util.ArrayList;
+import java.nio.ByteBuffer;
+
+/*
+ * Contains information about remote device specifically the player and features enabled on it along
+ * with an encapsulation of the current track and playlist information.
+ */
+class RemoteDevice {
+
+ /*
+ * Remote features from JNI
+ */
+ private static final int FEAT_NONE = 0;
+ private static final int FEAT_METADATA = 1;
+ private static final int FEAT_ABSOLUTE_VOLUME = 2;
+ private static final int FEAT_BROWSE = 4;
+
+ private static final int VOLUME_LABEL_UNDEFINED = -1;
+
+ final BluetoothDevice mBTDevice;
+ private int mRemoteFeatures;
+ private boolean mAbsVolNotificationRequested;
+ private boolean mFirstAbsVolCmdRecvd;
+ private int mNotificationLabel;
+
+ RemoteDevice(BluetoothDevice mDevice) {
+ mBTDevice = mDevice;
+ mRemoteFeatures = FEAT_NONE;
+ mAbsVolNotificationRequested = false;
+ mNotificationLabel = VOLUME_LABEL_UNDEFINED;
+ mFirstAbsVolCmdRecvd = false;
+ }
+
+ synchronized void setRemoteFeatures(int remoteFeatures) {
+ mRemoteFeatures = remoteFeatures;
+ }
+
+ synchronized public byte[] getBluetoothAddress() {
+ return Utils.getByteAddress(mBTDevice);
+ }
+
+ synchronized public void setNotificationLabel(int label) {
+ mNotificationLabel = label;
+ }
+
+ synchronized public int getNotificationLabel() {
+ return mNotificationLabel;
+ }
+
+ synchronized public void setAbsVolNotificationRequested(boolean request) {
+ mAbsVolNotificationRequested = request;
+ }
+
+ synchronized public boolean getAbsVolNotificationRequested() {
+ return mAbsVolNotificationRequested;
+ }
+
+ synchronized public void setFirstAbsVolCmdRecvd() {
+ mFirstAbsVolCmdRecvd = true;
+ }
+
+ synchronized public boolean getFirstAbsVolCmdRecvd() {
+ return mFirstAbsVolCmdRecvd;
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
new file mode 100644
index 0000000..033ea04
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.MediaMetadata;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+/*
+ * Contains information about tracks that either currently playing or maintained in playlist
+ * This is used as a local repository for information that will be passed on as MediaMetadata to the
+ * MediaSessionServicve
+ */
+class TrackInfo {
+ private static final String TAG = "AvrcpTrackInfo";
+ private static final boolean DBG = true;
+
+ /*
+ * Default values for each of the items from JNI
+ */
+ private static final int TRACK_NUM_INVALID = -1;
+ private static final int TOTAL_TRACKS_INVALID = -1;
+ private static final int TOTAL_TRACK_TIME_INVALID = -1;
+ private static final String UNPOPULATED_ATTRIBUTE = "";
+
+ /*
+ *Element Id Values for GetMetaData from JNI
+ */
+ 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 final String mArtistName;
+ private final String mTrackTitle;
+ private final String mAlbumTitle;
+ private final String mGenre;
+ private final long mTrackNum; // number of audio file on original recording.
+ private final long mTotalTracks;// total number of tracks on original recording
+ private final long mTrackLen;// full length of AudioFile.
+
+ public TrackInfo() {
+ this(new ArrayList<Integer>(), new ArrayList<String>());
+ }
+
+ public TrackInfo(List<Integer> attrIds, List<String> attrMap) {
+ Map<Integer, String> attributeMap = new HashMap<>();
+ for (int i = 0; i < attrIds.size(); i++) {
+ attributeMap.put(attrIds.get(i), attrMap.get(i));
+ }
+
+ String attribute;
+ mTrackTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_TITLE, UNPOPULATED_ATTRIBUTE);
+
+ mArtistName = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ARTIST_NAME, UNPOPULATED_ATTRIBUTE);
+
+ mAlbumTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ALBUM_NAME, UNPOPULATED_ATTRIBUTE);
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_TRACK_NUMBER);
+ mTrackNum = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TRACK_NUM_INVALID;
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER);
+ mTotalTracks = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TOTAL_TRACKS_INVALID;
+
+ mGenre = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_GENRE, UNPOPULATED_ATTRIBUTE);
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
+ mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TOTAL_TRACK_TIME_INVALID;
+ }
+
+ public String toString() {
+ return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle +
+ " albumTitle= " + mAlbumTitle + " genre= " +mGenre+" trackNum= "+
+ Long.toString(mTrackNum) + " track_len : "+ Long.toString(mTrackLen) +
+ " TotalTracks " + Long.toString(mTotalTracks) + "]";
+ }
+
+ public MediaMetadata getMediaMetaData() {
+ if (DBG) Log.d(TAG, " TrackInfo " + toString());
+ MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,
+ mArtistName);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,
+ mTrackTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM,
+ mAlbumTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE,
+ mGenre);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+ mTrackNum);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+ mTotalTracks);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+ mTrackLen);
+ return mMetaDataBuilder.build();
+ }
+
+
+ public String displayMetaData() {
+ MediaMetadata metaData = getMediaMetaData();
+ StringBuffer sb = new StringBuffer();
+ /* getDescription only contains artist, title and album */
+ sb.append(metaData.getDescription().toString() + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE))
+ sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID))
+ sb.append(metaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index afb9d8d..6bae460 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -28,7 +28,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.hdp.HealthService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hfpclient.HeadsetClientService;