Merge changes Ia4feba10,I8f8d11b2
* changes:
Refactor A2dpSink and AvrcpController
Move silent player to A2DPSink, get button routing only if focus is granted
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index 50c5087..91de5af 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -231,7 +231,7 @@
int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) {
return jniRegisterNativeMethods(
- env, "com/android/bluetooth/a2dpsink/A2dpSinkStateMachine", sMethods,
+ env, "com/android/bluetooth/a2dpsink/A2dpSinkService", sMethods,
NELEM(sMethods));
}
}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 3815411..2d9e87b 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -71,7 +71,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for passthrough response");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -109,7 +109,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for connection state");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -134,7 +134,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -158,7 +158,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -184,7 +184,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -202,7 +202,7 @@
ScopedLocalRef<jbyteArray> playerattribs(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
if (!playerattribs.get()) {
- ALOGE("Fail to new jbyteArray playerattribs ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -236,7 +236,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -279,7 +279,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -303,7 +303,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -333,7 +333,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -388,7 +388,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -411,7 +411,7 @@
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -436,6 +436,16 @@
return;
}
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
// Inspect if the first element is a folder/item or player listing. They are
// always exclusive.
bool isPlayerListing =
@@ -468,17 +478,7 @@
return;
}
// Parse UID
- ScopedLocalRef<jbyteArray> uidByteArray(
- sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
- sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
- BTRC_UID_SIZE * sizeof(uint8_t),
- (jbyte*)item->media.uid);
-
+ long long uid = *(long long*)item->media.uid;
// Parse Attrs
ScopedLocalRef<jintArray> attrIdArray(
sCallbackEnv.get(),
@@ -504,10 +504,6 @@
ScopedLocalRef<jstring> attrValStr(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((char*)(item->media.p_attrs[j].text)));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
sCallbackEnv->SetObjectArrayElement(attrValArray.get(), j,
attrValStr.get());
}
@@ -515,9 +511,9 @@
ScopedLocalRef<jobject> mediaObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
- sCallbacksObj, method_createFromNativeMediaItem,
- uidByteArray.get(), (jint)item->media.type, mediaName.get(),
- attrIdArray.get(), attrValArray.get()));
+ sCallbacksObj, method_createFromNativeMediaItem, uid,
+ (jint)item->media.type, mediaName.get(), attrIdArray.get(),
+ attrValArray.get()));
if (!mediaObj.get()) {
ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
return;
@@ -536,22 +532,12 @@
return;
}
// Parse UID
- ScopedLocalRef<jbyteArray> uidByteArray(
- sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
- sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
- BTRC_UID_SIZE * sizeof(uint8_t),
- (jbyte*)item->folder.uid);
-
+ long long uid = *(long long*)item->folder.uid;
ScopedLocalRef<jobject> folderObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
- sCallbacksObj, method_createFromNativeFolderItem,
- uidByteArray.get(), (jint)item->folder.type, folderName.get(),
+ sCallbacksObj, method_createFromNativeFolderItem, uid,
+ (jint)item->folder.type, folderName.get(),
(jint)item->folder.playable));
if (!folderObj.get()) {
ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
@@ -609,10 +595,10 @@
if (isPlayerListing) {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
- itemArray.get());
+ addr.get(), itemArray.get());
} else {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
- status, itemArray.get());
+ addr.get(), status, itemArray.get());
}
}
@@ -626,9 +612,18 @@
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
- (jint)count);
+ addr.get(), (jint)count);
}
static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
@@ -642,9 +637,18 @@
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
- (jint)num_items, (jint)depth);
+ addr.get(), (jint)num_items, (jint)depth);
}
static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
@@ -657,9 +661,19 @@
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
- sCallbackEnv->CallVoidMethod(
- sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status);
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
+ sCallbackEnv->CallVoidMethod(sCallbacksObj,
+ method_handleSetAddressedPlayerRsp, addr.get(),
+ (jint)status);
}
static void btavrcp_addressed_player_changed_callback(const RawAddress& bd_addr,
@@ -672,9 +686,18 @@
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
- sCallbackEnv->CallVoidMethod(sCallbacksObj,
- method_handleAddressedPlayerChanged, (jint)id);
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_handleAddressedPlayerChanged, addr.get(), (jint)id);
}
static void btavrcp_now_playing_content_changed_callback(
@@ -683,9 +706,18 @@
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
- sCallbackEnv->CallVoidMethod(sCallbacksObj,
- method_handleNowPlayingContentChanged);
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -747,32 +779,32 @@
method_handleGetFolderItemsRsp =
env->GetMethodID(clazz, "handleGetFolderItemsRsp",
- "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V");
+ "([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
method_handleGetPlayerItemsRsp = env->GetMethodID(
clazz, "handleGetPlayerItemsRsp",
- "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+ "([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
method_createFromNativeMediaItem =
env->GetMethodID(clazz, "createFromNativeMediaItem",
- "([BILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
+ "(JILjava/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;");
+ "(JILjava/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");
+ env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
method_handleSetBrowsedPlayerRsp =
- env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
+ env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "([BII)V");
method_handleSetAddressedPlayerRsp =
- env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)V");
+ env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "([BI)V");
method_handleAddressedPlayerChanged =
- env->GetMethodID(clazz, "handleAddressedPlayerChanged", "(I)V");
+ env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
method_handleNowPlayingContentChanged =
- env->GetMethodID(clazz, "handleNowPlayingContentChanged", "()V");
+ env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
ALOGI("%s: succeeds", __func__);
}
@@ -1081,7 +1113,7 @@
static void changeFolderPathNative(JNIEnv* env, jobject object,
jbyteArray address, jbyte direction,
- jbyteArray uidarr) {
+ jlong uid) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
@@ -1089,22 +1121,22 @@
return;
}
- jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
- if (!uid) {
- jniThrowIOException(env, EINVAL);
- return;
- }
+ // jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
+ // if (!uid) {
+ // jniThrowIOException(env, EINVAL);
+ // return;
+ //}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->change_folder_path_cmd(
- rawAddress, (uint8_t)direction, (uint8_t*)uid);
+ rawAddress, (uint8_t)direction, (uint8_t*)&uid);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
}
- env->ReleaseByteArrayElements(address, addr, 0);
+ // env->ReleaseByteArrayElements(address, addr, 0);
}
static void setBrowsedPlayerNative(JNIEnv* env, jobject object,
@@ -1149,7 +1181,7 @@
}
static void playItemNative(JNIEnv* env, jobject object, jbyteArray address,
- jbyte scope, jbyteArray uidArr, jint uidCounter) {
+ jbyte scope, jlong uid, jint uidCounter) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
@@ -1157,17 +1189,17 @@
return;
}
- jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
- if (!uid) {
- jniThrowIOException(env, EINVAL);
- return;
- }
+ // jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
+ // if (!uid) {
+ // jniThrowIOException(env, EINVAL);
+ // return;
+ // }
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
bt_status_t status = sBluetoothAvrcpInterface->play_item_cmd(
- rawAddress, (uint8_t)scope, (uint8_t*)uid, (uint16_t)uidCounter);
+ rawAddress, (uint8_t)scope, (uint8_t*)&uid, (uint16_t)uidCounter);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending playItemNative command, status: %d", status);
}
@@ -1191,8 +1223,8 @@
{"getNowPlayingListNative", "([BII)V", (void*)getNowPlayingListNative},
{"getFolderListNative", "([BII)V", (void*)getFolderListNative},
{"getPlayerListNative", "([BII)V", (void*)getPlayerListNative},
- {"changeFolderPathNative", "([BB[B)V", (void*)changeFolderPathNative},
- {"playItemNative", "([BB[BI)V", (void*)playItemNative},
+ {"changeFolderPathNative", "([BBJ)V", (void*)changeFolderPathNative},
+ {"playItemNative", "([BBJI)V", (void*)playItemNative},
{"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
{"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
};
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 8292b53..e951ed5 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -13,222 +13,94 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.bluetooth.a2dpsink;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothA2dpSink;
-import android.content.Intent;
import android.util.Log;
import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
* @hide
*/
public class A2dpSinkService extends ProfileService {
- private static final boolean DBG = true;
private static final String TAG = "A2dpSinkService";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ static final int MAXIMUM_CONNECTED_DEVICES = 1;
- private A2dpSinkStateMachine mStateMachine;
- private static A2dpSinkService sA2dpSinkService;
+ private final BluetoothAdapter mAdapter;
+ protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
+ new ConcurrentHashMap<>(1);
- @Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothA2dpSinkBinder(this);
+ private A2dpSinkStreamHandler mA2dpSinkStreamHandler;
+ private static A2dpSinkService sService;
+
+ static {
+ classInitNative();
}
@Override
protected boolean start() {
- if (DBG) {
- Log.d(TAG, "start()");
- }
- // Start the media browser service.
- Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
- startService(startIntent);
- mStateMachine = A2dpSinkStateMachine.make(this, this);
- setA2dpSinkService(this);
+ initNative();
+ sService = this;
+ mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
return true;
}
@Override
protected boolean stop() {
- if (DBG) {
- Log.d(TAG, "stop()");
+ for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+ stateMachine.quitNow();
}
- setA2dpSinkService(null);
- if (mStateMachine != null) {
- mStateMachine.doQuit();
- }
- Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
- stopService(stopIntent);
+ sService = null;
return true;
}
+ public static A2dpSinkService getA2dpSinkService() {
+ return sService;
+ }
+
+ public A2dpSinkService() {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ protected A2dpSinkStateMachine newStateMachine(BluetoothDevice device) {
+ return new A2dpSinkStateMachine(device, this);
+ }
+
+ protected synchronized A2dpSinkStateMachine getStateMachine(BluetoothDevice device) {
+ return mDeviceStateMap.get(device);
+ }
+
+ /**
+ * Request audio focus such that the designated device can stream audio
+ */
+ public void requestAudioFocus(BluetoothDevice device, boolean request) {
+ mA2dpSinkStreamHandler.requestAudioFocus(request);
+ }
+
@Override
- protected void cleanup() {
- if (mStateMachine != null) {
- mStateMachine.cleanup();
- }
- }
-
- //API Methods
-
- public static synchronized A2dpSinkService getA2dpSinkService() {
- if (sA2dpSinkService == null) {
- Log.w(TAG, "getA2dpSinkService(): service is null");
- return null;
- }
- if (!sA2dpSinkService.isAvailable()) {
- Log.w(TAG, "getA2dpSinkService(): service is not available ");
- return null;
- }
- return sA2dpSinkService;
- }
-
- private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
- if (DBG) {
- Log.d(TAG, "setA2dpSinkService(): set to: " + instance);
- }
- sA2dpSinkService = instance;
- }
-
- public boolean connect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- int connectionState = mStateMachine.getConnectionState(device);
- if (connectionState == BluetoothProfile.STATE_CONNECTED
- || connectionState == BluetoothProfile.STATE_CONNECTING) {
- return false;
- }
-
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- return false;
- }
-
- mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
- return true;
- }
-
- boolean disconnect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- int connectionState = mStateMachine.getConnectionState(device);
- if (connectionState != BluetoothProfile.STATE_CONNECTED
- && connectionState != BluetoothProfile.STATE_CONNECTING) {
- return false;
- }
-
- mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
- return true;
- }
-
- public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectedDevices();
- }
-
- List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getDevicesMatchingConnectionStates(states);
- }
-
- int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectionState(device);
- }
-
- public boolean setPriority(BluetoothDevice device, int priority) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
- }
- AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
- return true;
- }
-
- public int getPriority(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
- }
-
- /**
- * Called by AVRCP controller to provide information about the last user intent on CT.
- *
- * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
- * any incoming sound from the phone (and also retain focus for a few seconds before
- * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
- * component will take the focus away but also notify the stack to throw away incoming data.
- */
- public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- if (mStateMachine != null) {
- if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY
- && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
- } 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);
- }
- }
- }
-
- /**
- * Called by AVRCP controller to provide information about the last user intent on TG.
- *
- * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
- * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
- * stopping playback.
- */
- public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
- if (mStateMachine != null) {
- if (!isPlaying) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
- } else {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
- }
- }
- }
-
- /**
- * Called by AVRCP controller to establish audio focus.
- *
- * In order to perform streaming the A2DP sink must have audio focus. This interface allows the
- * associated MediaSession to inform the sink of intent to play and then allows streaming to be
- * started from either the source or the sink endpoint.
- */
- public void requestAudioFocus(BluetoothDevice device, boolean request) {
- if (mStateMachine != null) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
- }
- }
-
- synchronized boolean isA2dpPlaying(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (DBG) {
- Log.d(TAG, "isA2dpPlaying(" + device + ")");
- }
- return mStateMachine.isPlaying(device);
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getAudioConfig(device);
+ protected IProfileServiceBinder initBinder() {
+ return new A2dpSinkServiceBinder(this);
}
//Binder object: Must be static class or memory leak may occur
- private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
+ private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
implements IProfileServiceBinder {
private A2dpSinkService mService;
@@ -238,13 +110,13 @@
return null;
}
- if (mService != null && mService.isAvailable()) {
+ if (mService != null) {
return mService;
}
return null;
}
- BluetoothA2dpSinkBinder(A2dpSinkService svc) {
+ A2dpSinkServiceBinder(A2dpSinkService svc) {
mService = svc;
}
@@ -299,15 +171,6 @@
}
@Override
- public boolean isA2dpPlaying(BluetoothDevice device) {
- A2dpSinkService service = getService();
- if (service == null) {
- return false;
- }
- return service.isA2dpPlaying(device);
- }
-
- @Override
public boolean setPriority(BluetoothDevice device, int priority) {
A2dpSinkService service = getService();
if (service == null) {
@@ -326,6 +189,15 @@
}
@Override
+ public boolean isA2dpPlaying(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.isA2dpPlaying(device);
+ }
+
+ @Override
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
A2dpSinkService service = getService();
if (service == null) {
@@ -335,13 +207,219 @@
}
}
- ;
+ /* Generic Profile Code */
+
+ /**
+ * Connect the given Bluetooth device.
+ *
+ * @return true if connection is successful, false otherwise.
+ */
+ public synchronized boolean connect(BluetoothDevice device) {
+ if (device == null) {
+ throw new IllegalArgumentException("Null device");
+ }
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, " connect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.connect();
+ return true;
+ } else {
+ // a state machine instance doesn't exist yet, and the max has been reached.
+ Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
+ + "Connect request rejected on " + device);
+ return false;
+
+ }
+ }
+
+ /**
+ * Disconnect the given Bluetooth device.
+ *
+ * @return true if disconnect is successful, false otherwise.
+ */
+ public synchronized boolean disconnect(BluetoothDevice device) {
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "A2DP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return false;
+ }
+ int connectionState = stateMachine.getState();
+ if (connectionState == BluetoothProfile.STATE_DISCONNECTED
+ || connectionState == BluetoothProfile.STATE_DISCONNECTING) {
+ return false;
+ }
+ // upon completion of disconnect, the state machine will remove itself from the available
+ // devices map
+ stateMachine.disconnect();
+ return true;
+ }
+
+ void removeStateMachine(A2dpSinkStateMachine stateMachine) {
+ mDeviceStateMap.remove(stateMachine.getDevice());
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+ }
+
+ protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
+ stateMachine = newStateMachine(device);
+ mDeviceStateMap.put(device, stateMachine);
+ stateMachine.start();
+ }
+ return stateMachine;
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+ List<BluetoothDevice> deviceList = new ArrayList<>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ for (BluetoothDevice device : bondedDevices) {
+ connectionState = getConnectionState(device);
+ if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+ for (int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ if (DBG) Log.d(TAG, deviceList.toString());
+ Log.d(TAG, "GetDevicesDone");
+ return deviceList;
+ }
+
+ synchronized int getConnectionState(BluetoothDevice device) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+ : stateMachine.getState();
+ }
+
+ /**
+ * Set the priority of the profile.
+ *
+ * @param device the remote device
+ * @param priority the priority of the profile
+ * @return true on success, otherwise false
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ if (DBG) {
+ Log.d(TAG, "Saved priority " + device + " = " + priority);
+ }
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
+ return true;
+ }
+
+ /**
+ * Get the priority of the profile.
+ *
+ * @param device the remote device
+ * @return priority of the specified device
+ */
+ public int getPriority(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
+ }
+
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- if (mStateMachine != null) {
- mStateMachine.dump(sb);
+ ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+ for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+ ProfileService.println(sb,
+ "==== StateMachine for " + stateMachine.getDevice() + " ====");
+ stateMachine.dump(sb);
}
}
+
+ /**
+ * Get the current Bluetooth Audio focus state
+ *
+ * @return focus
+ */
+ public static int getFocusState() {
+ return sService.mA2dpSinkStreamHandler.getFocusState();
+ }
+
+ boolean isA2dpPlaying(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mA2dpSinkStreamHandler.isPlaying();
+ }
+
+ BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return null;
+ }
+ return stateMachine.getAudioConfig();
+ }
+
+ /* JNI interfaces*/
+
+ private static native void classInitNative();
+
+ private native void initNative();
+
+ private native void cleanupNative();
+
+ native boolean connectA2dpNative(byte[] address);
+
+ native boolean disconnectA2dpNative(byte[] address);
+
+ /**
+ * inform A2DP decoder of the current audio focus
+ *
+ * @param focusGranted
+ */
+ @VisibleForTesting
+ public native void informAudioFocusStateNative(int focusGranted);
+
+ /**
+ * inform A2DP decoder the desired audio gain
+ *
+ * @param gain
+ */
+ @VisibleForTesting
+ public native void informAudioTrackGainNative(float gain);
+
+ private void onConnectionStateChanged(byte[] address, int state) {
+ StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
+
+ private void onAudioStateChanged(byte[] address, int state) {
+ if (state == StackEvent.AUDIO_STATE_STARTED) {
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+ } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+ }
+ }
+
+ private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+ StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
+ channelCount);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index ed77281..19ed87f 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -13,888 +13,288 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-/**
- * Bluetooth A2dp Sink StateMachine
- * (Disconnected)
- * | ^
- * CONNECT | | DISCONNECTED
- * V |
- * (Pending)
- * | ^
- * CONNECTED | | CONNECT
- * V |
- * (Connected -- See A2dpSinkStreamHandler)
- */
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.os.Handler;
import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
public class A2dpSinkStateMachine extends StateMachine {
- private static final boolean DBG = false;
+ static final String TAG = "A2DPSinkStateMachine";
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
- static final int CONNECT = 1;
- static final int DISCONNECT = 2;
- private static final int STACK_EVENT = 101;
- private static final int CONNECT_TIMEOUT = 201;
- public static final int EVENT_AVRCP_CT_PLAY = 301;
- public static final int EVENT_AVRCP_CT_PAUSE = 302;
- public static final int EVENT_AVRCP_TG_PLAY = 303;
- public static final int EVENT_AVRCP_TG_PAUSE = 304;
- public static final int EVENT_REQUEST_FOCUS = 305;
+ //0->99 Events from Outside
+ public static final int CONNECT = 1;
+ public static final int DISCONNECT = 2;
- private static final int IS_INVALID_DEVICE = 0;
- private static final int IS_VALID_DEVICE = 1;
- private static final int CONNECT_TIMEOUT_MS = 5000;
- public static final int AVRC_ID_PLAY = 0x44;
- public static final int AVRC_ID_PAUSE = 0x46;
- public static final int KEY_STATE_PRESSED = 0;
- public static final int KEY_STATE_RELEASED = 1;
+ //100->199 Internal Events
+ protected static final int CLEANUP = 100;
+ private static final int CONNECT_TIMEOUT = 101;
- // Connection states.
- // 1. Disconnected: The connection does not exist.
- // 2. Pending: The connection is being established.
- // 3. Connected: The connection is established. The audio connection is in Idle state.
- private Disconnected mDisconnected;
- private Pending mPending;
- private Connected mConnected;
+ //200->299 Events from Native
+ static final int STACK_EVENT = 200;
- private A2dpSinkService mService;
- private Context mContext;
- private BluetoothAdapter mAdapter;
- private IntentBroadcastHandler mIntentBroadcastHandler;
+ static final int CONNECT_TIMEOUT_MS = 5000;
- private static final int MSG_CONNECTION_STATE_CHANGED = 0;
+ protected final BluetoothDevice mDevice;
+ protected final byte[] mDeviceAddress;
+ protected final A2dpSinkService mService;
+ protected final Disconnected mDisconnected;
+ protected final Connecting mConnecting;
+ protected final Connected mConnected;
+ protected final Disconnecting mDisconnecting;
- private final Object mLockForPatch = new Object();
+ protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
+ protected BluetoothAudioConfig mAudioConfig = null;
- // mCurrentDevice is the device connected before the state changes
- // mTargetDevice is the device to be connected
- // mIncomingDevice is the device connecting to us, valid only in Pending state
- // when mIncomingDevice is not null, both mCurrentDevice
- // and mTargetDevice are null
- // when either mCurrentDevice or mTargetDevice is not null,
- // mIncomingDevice is null
- // Stable states
- // No connection, Disconnected state
- // both mCurrentDevice and mTargetDevice are null
- // Connected, Connected state
- // mCurrentDevice is not null, mTargetDevice is null
- // Interim states
- // Connecting to a device, Pending
- // mCurrentDevice is null, mTargetDevice is not null
- // Disconnecting device, Connecting to new device
- // Pending
- // Both mCurrentDevice and mTargetDevice are not null
- // Disconnecting device Pending
- // mCurrentDevice is not null, mTargetDevice is null
- // Incoming connections Pending
- // Both mCurrentDevice and mTargetDevice are null
- private BluetoothDevice mCurrentDevice = null;
- private BluetoothDevice mTargetDevice = null;
- private BluetoothDevice mIncomingDevice = null;
- private BluetoothDevice mPlayingDevice = null;
- private A2dpSinkStreamHandler mStreaming = null;
-
- private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
- new HashMap<BluetoothDevice, BluetoothAudioConfig>();
-
- static {
- classInitNative();
- }
-
- private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
- super("A2dpSinkStateMachine");
- mService = svc;
- mContext = context;
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- initNative();
+ A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service) {
+ super(TAG);
+ mDevice = device;
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ if (DBG) Log.d(TAG, device.toString());
mDisconnected = new Disconnected();
- mPending = new Pending();
+ mConnecting = new Connecting();
mConnected = new Connected();
+ mDisconnecting = new Disconnecting();
addState(mDisconnected);
- addState(mPending);
+ addState(mConnecting);
addState(mConnected);
+ addState(mDisconnecting);
setInitialState(mDisconnected);
-
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
- mIntentBroadcastHandler = new IntentBroadcastHandler();
}
- static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
- Log.d("A2dpSinkStateMachine", "make");
- A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
- a2dpSm.start();
- return a2dpSm;
+ protected String getConnectionStateChangedIntent() {
+ return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
}
- public void doQuit() {
- if (DBG) {
- Log.d("A2dpSinkStateMachine", "Quit");
- }
- synchronized (A2dpSinkStateMachine.this) {
- mStreaming = null;
- }
- quitNow();
+ /**
+ * Get the current connection state
+ *
+ * @return current State
+ */
+ public int getState() {
+ return mMostRecentState;
}
- public void cleanup() {
- cleanupNative();
- mAudioConfigs.clear();
+ /**
+ * get current audio config
+ */
+ BluetoothAudioConfig getAudioConfig() {
+ return mAudioConfig;
}
+ /**
+ * Get the underlying device tracked by this state machine
+ *
+ * @return device in focus
+ */
+ public synchronized BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * send the Connect command asynchronously
+ */
+ public final void connect() {
+ sendMessage(CONNECT);
+ }
+
+ /**
+ * send the Disconnect command asynchronously
+ */
+ public final void disconnect() {
+ sendMessage(DISCONNECT);
+ }
+
+ /**
+ * Dump the current State Machine to the string builder.
+ * @param sb output string
+ */
public void dump(StringBuilder sb) {
- if (mCurrentDevice == null) return;
- ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
- + mCurrentDevice.getName() + ") " + this.toString());
- ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
- ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
+ ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
}
- private class Disconnected extends State {
+ @Override
+ protected void unhandledMessage(Message msg) {
+ Log.w(TAG, "unhandledMessage in state " + getCurrentState() + "msg.what=" + msg.what);
+ }
+
+ class Disconnected extends State {
@Override
public void enter() {
- log("Enter Disconnected: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
}
@Override
public boolean processMessage(Message message) {
- log("Disconnected process message: " + message.what);
- if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
- loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
- return NOT_HANDLED;
- }
-
- boolean retValue = HANDLED;
switch (message.what) {
- case CONNECT:
- BluetoothDevice device = (BluetoothDevice) message.obj;
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTED);
-
- if (!connectA2dpNative(getByteAddress(device))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- break;
- }
-
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = device;
- transitionTo(mPending);
- }
- break;
- case DISCONNECT:
- // ignore
- break;
case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
- default:
- return NOT_HANDLED;
+ processStackEvent((StackEvent) message.obj);
+ return true;
+ case CONNECT:
+ if (DBG) Log.d(TAG, "Connect");
+ transitionTo(mConnecting);
+ return true;
+ case CLEANUP:
+ mService.removeStateMachine(A2dpSinkStateMachine.this);
+ return true;
}
- return retValue;
+ return false;
}
- @Override
- public void exit() {
- log("Exit Disconnected: " + getCurrentMessage().what);
- }
-
- // in Disconnected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- logw("Ignore A2DP DISCONNECTED event, device: " + device);
- break;
- case CONNECTION_STATE_CONNECTING:
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = device;
- transitionTo(mPending);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- logw("A2DP Connected from Disconnected state");
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = device;
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_CONNECTED:
transitionTo(mConnected);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ sendMessage(CLEANUP);
+ break;
}
- break;
- case CONNECTION_STATE_DISCONNECTING:
- logw("Ignore HF DISCONNECTING event, device: " + device);
- break;
- default:
- loge("Incorrect state: " + state);
- break;
}
}
}
- private class Pending extends State {
+ class Connecting extends State {
+ boolean mIncommingConnection = false;
+
@Override
public void enter() {
- log("Enter Pending: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Connecting");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS);
+
+ if (!mIncommingConnection) {
+ mService.connectA2dpNative(mDeviceAddress);
+ }
+
+ super.enter();
}
@Override
public boolean processMessage(Message message) {
- log("Pending process message: " + message.what);
-
- boolean retValue = HANDLED;
switch (message.what) {
- case CONNECT:
- logd("Disconnect before connecting to another target");
- break;
- case CONNECT_TIMEOUT:
- onConnectionStateChanged(getByteAddress(mTargetDevice),
- CONNECTION_STATE_DISCONNECTED);
- break;
- case DISCONNECT:
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (mCurrentDevice != null && mTargetDevice != null && mTargetDevice.equals(
- device)) {
- // cancel connection to the mTargetDevice
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- }
- }
- break;
case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- log("STACK_EVENT " + event.type);
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
- default:
- return NOT_HANDLED;
+ processStackEvent((StackEvent) message.obj);
+ return true;
+ case CONNECT_TIMEOUT:
+ transitionTo(mDisconnected);
+ return true;
}
- return retValue;
+ return false;
}
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_CONNECTED:
+ transitionTo(mConnected);
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ transitionTo(mDisconnected);
+ break;
+ }
+ }
+ }
@Override
public void exit() {
removeMessages(CONNECT_TIMEOUT);
}
- // in Pending state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- log("processConnectionEvent state " + state);
- log("Devices curr: " + mCurrentDevice + " target: " + mTargetDevice + " incoming: "
- + mIncomingDevice + " device: " + device);
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_DISCONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = null;
- }
-
- if (mTargetDevice != null) {
- if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
- broadcastConnectionState(mTargetDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mDisconnected);
- }
- }
- } else {
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = null;
- transitionTo(mDisconnected);
- }
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- // outgoing connection failed
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mDisconnected);
- }
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- broadcastConnectionState(mIncomingDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = null;
- transitionTo(mDisconnected);
- }
- } else {
- loge("Unknown device Disconnected: " + device);
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- loge("current device is not null");
- // disconnection failed
- broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTING);
- if (mTargetDevice != null) {
- broadcastConnectionState(mTargetDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- }
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- loge("target device is not null");
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = mTargetDevice;
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("incoming device is not null");
- broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = mIncomingDevice;
- mIncomingDevice = null;
- transitionTo(mConnected);
- }
- } else {
- loge("Unknown device Connected: " + device);
- // something is wrong here, but sync our state with stack by connecting to
- // the new device and disconnect from previous device.
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = device;
- mTargetDevice = null;
- mIncomingDevice = null;
- transitionTo(mConnected);
- }
- }
- break;
- case CONNECTION_STATE_CONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- log("current device tries to connect back");
- // TODO(BT) ignore or reject
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- // The stack is connecting to target device or
- // there is an incoming connection from the target device at the same time
- // we already broadcasted the intent, doing nothing here
- log("Stack and target device are connecting");
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("Another connecting event on the incoming device");
- } else {
- // We get an incoming connecting request while Pending
- // TODO(BT) is stack handing this case? let's ignore it for now
- log("Incoming connection while pending, ignore");
- }
- break;
- case CONNECTION_STATE_DISCONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- // we already broadcasted the intent, doing nothing here
- if (DBG) {
- log("stack is disconnecting mCurrentDevice");
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- loge("TargetDevice is getting disconnected");
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("IncomingDevice is getting disconnected");
- } else {
- loge("Disconnecting unknown device: " + device);
- }
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
-
}
- private class Connected extends State {
+ class Connected extends State {
@Override
public void enter() {
- log("Enter Connected: " + getCurrentMessage().what);
- // Upon connected, the audio starts out as stopped
- broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- synchronized (A2dpSinkStateMachine.this) {
- if (mStreaming == null) {
- if (DBG) {
- log("Creating New A2dpSinkStreamHandler");
- }
- mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
- }
- }
- if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
- informAudioFocusStateNative(0);
- }
+ if (DBG) Log.d(TAG, "Enter Connected");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
}
@Override
public boolean processMessage(Message message) {
- log("Connected process message: " + message.what);
- if (mCurrentDevice == null) {
- loge("ERROR: mCurrentDevice is null in Connected");
- return NOT_HANDLED;
- }
-
switch (message.what) {
- case CONNECT:
- logd("Disconnect before connecting to another target");
- break;
-
- case DISCONNECT: {
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mCurrentDevice.equals(device)) {
- break;
- }
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTED);
- if (!disconnectA2dpNative(getByteAddress(device))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- break;
- }
- mPlayingDevice = null;
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
- transitionTo(mPending);
- }
- break;
-
+ case DISCONNECT:
+ transitionTo(mDisconnecting);
+ mService.disconnectA2dpNative(mDeviceAddress);
+ return true;
case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_STATE_CHANGED:
- processAudioStateEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
-
- case EVENT_AVRCP_CT_PLAY:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY).sendToTarget();
- break;
-
- case EVENT_AVRCP_TG_PLAY:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY).sendToTarget();
- break;
-
- case EVENT_AVRCP_CT_PAUSE:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE).sendToTarget();
- break;
-
- case EVENT_AVRCP_TG_PAUSE:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget();
- break;
-
- case EVENT_REQUEST_FOCUS:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
- break;
-
- default:
- return NOT_HANDLED;
+ processStackEvent((StackEvent) message.obj);
+ return true;
}
- return HANDLED;
- }
-
- // in Connected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
- mPlayingDevice = null;
- }
- if (mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- // Take care of existing audio focus in the streaming state machine.
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
- .sendToTarget();
- mCurrentDevice = null;
- transitionTo(mDisconnected);
- }
- } else {
- loge("Disconnected from unknown device: " + device);
- }
- break;
- default:
- loge("Connection State Device: " + device + " bad state: " + state);
- break;
- }
- }
-
- private void processAudioStateEvent(BluetoothDevice device, int state) {
- if (!mCurrentDevice.equals(device)) {
- loge("Audio State Device:" + device + "is different from ConnectedDevice:"
- + mCurrentDevice);
- return;
- }
- log(" processAudioStateEvent in state " + state);
- switch (state) {
- case AUDIO_STATE_STARTED:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
- if (mPlayingDevice == null) {
- mPlayingDevice = device;
- broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
- BluetoothA2dpSink.STATE_NOT_PLAYING);
- }
- break;
- case AUDIO_STATE_REMOTE_SUSPEND:
- case AUDIO_STATE_STOPPED:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
- if (mPlayingDevice != null) {
- broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- mPlayingDevice = null;
- }
- break;
- default:
- loge("Audio State Device: " + device + " bad state: " + state);
- break;
- }
- }
- }
-
- private void processAudioConfigEvent(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
- log("processAudioConfigEvent: " + device);
- mAudioConfigs.put(device, audioConfig);
- broadcastAudioConfig(device, audioConfig);
- }
-
- int getConnectionState(BluetoothDevice device) {
- if (getCurrentState() == mDisconnected) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
- synchronized (this) {
- IState currentState = getCurrentState();
- if (currentState == mPending) {
- if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTING;
- }
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- return BluetoothProfile.STATE_DISCONNECTING;
- }
- if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTING; // incoming connection
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
- if (currentState == mConnected) {
- if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTED;
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- } else {
- loge("Bad currentState: " + currentState);
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- }
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- return mAudioConfigs.get(device);
- }
-
- List<BluetoothDevice> getConnectedDevices() {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- synchronized (this) {
- if (getCurrentState() == mConnected) {
- devices.add(mCurrentDevice);
- }
- }
- return devices;
- }
-
- boolean isPlaying(BluetoothDevice device) {
- synchronized (this) {
- if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
- return true;
- }
- }
- return false;
- }
-
- // Utility Functions
- boolean okToConnect(BluetoothDevice device) {
- AdapterService adapterService = AdapterService.getAdapterService();
- 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;
- }
- logw("okToConnect not OK to connect " + device);
- return false;
- }
-
- synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- int connectionState;
-
- for (BluetoothDevice device : bondedDevices) {
- ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
- continue;
- }
- connectionState = getConnectionState(device);
- for (int i = 0; i < states.length; i++) {
- if (connectionState == states[i]) {
- deviceList.add(device);
- }
- }
- }
- return deviceList;
- }
-
-
- // This method does not check for error conditon (newState == prevState)
- private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
-
- int delay = 0;
- mIntentBroadcastHandler.sendMessageDelayed(
- mIntentBroadcastHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGED, prevState,
- newState, device), delay);
- }
-
- private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
- log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
- }
-
- private void broadcastAudioConfig(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_AUDIO_CONFIG_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothA2dpSink.EXTRA_AUDIO_CONFIG, audioConfig);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
- log("A2DP Audio Config : device: " + device + " config: " + audioConfig);
- }
-
- private byte[] getByteAddress(BluetoothDevice device) {
- return Utils.getBytesFromAddress(device.getAddress());
- }
-
- private void onConnectionStateChanged(byte[] address, int state) {
- StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
- event.device = getDevice(address);
- event.valueInt = state;
- sendMessage(STACK_EVENT, event);
- }
-
- private void onAudioStateChanged(byte[] address, int state) {
- StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
- event.device = getDevice(address);
- event.valueInt = state;
- sendMessage(STACK_EVENT, event);
- }
-
- private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
- StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_CONFIG_CHANGED);
- event.device = getDevice(address);
- int channelConfig =
- (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
- event.audioConfig =
- new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
- sendMessage(STACK_EVENT, event);
- }
-
- private BluetoothDevice getDevice(byte[] address) {
- return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
- }
-
- private class StackEvent {
- public int type = EVENT_TYPE_NONE;
- public BluetoothDevice device = null;
- public int valueInt = 0;
- public BluetoothAudioConfig audioConfig = null;
-
- private StackEvent(int type) {
- this.type = type;
- }
- }
-
- /** Handles A2DP connection state change intent broadcasts. */
- private class IntentBroadcastHandler extends Handler {
-
- private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
- if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) {
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
- }
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- log("Connection state " + device + ": " + prevState + "->" + state);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONNECTION_STATE_CHANGED:
- onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- }
- }
-
- public boolean sendPassThruPlay(BluetoothDevice mDevice) {
- log("sendPassThruPlay + ");
- AvrcpControllerService avrcpCtrlService =
- AvrcpControllerService.getAvrcpControllerService();
- if ((avrcpCtrlService != null) && (mDevice != null)
- && (avrcpCtrlService.getConnectedDevices().contains(mDevice))) {
- avrcpCtrlService.sendPassThroughCmd(mDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpCtrlService.sendPassThroughCmd(mDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_RELEASED);
- log(" sendPassThruPlay command sent - ");
- return true;
- } else {
- log("passthru command not sent, connection unavailable");
return false;
}
+
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_DISCONNECTING:
+ transitionTo(mDisconnecting);
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ transitionTo(mDisconnected);
+ break;
+ }
+ break;
+ case StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ mAudioConfig = new BluetoothAudioConfig(event.mSampleRate, event.mChannelCount,
+ AudioFormat.ENCODING_PCM_16BIT);
+ break;
+ }
+ }
}
- // Event types for STACK_EVENT message
- private static final int EVENT_TYPE_NONE = 0;
- private static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
- private static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
- private static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+ protected class Disconnecting extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Disconnecting");
+ onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+ transitionTo(mDisconnected);
+ }
+ }
- // Do not modify without updating the HAL bt_av.h files.
-
- // match up with btav_connection_state_t enum of bt_av.h
- static final int CONNECTION_STATE_DISCONNECTED = 0;
- static final int CONNECTION_STATE_CONNECTING = 1;
- static final int CONNECTION_STATE_CONNECTED = 2;
- static final int CONNECTION_STATE_DISCONNECTING = 3;
-
- // match up with btav_audio_state_t enum of bt_av.h
- static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
- static final int AUDIO_STATE_STOPPED = 1;
- static final int AUDIO_STATE_STARTED = 2;
-
- private static native void classInitNative();
-
- private native void initNative();
-
- private native void cleanupNative();
-
- private native boolean connectA2dpNative(byte[] address);
-
- private native boolean disconnectA2dpNative(byte[] address);
-
- public native void informAudioFocusStateNative(int focusGranted);
-
- public native void informAudioTrackGainNative(float focusGranted);
+ protected void onConnectionStateChanged(int currentState) {
+ if (mMostRecentState == currentState) {
+ return;
+ }
+ if (currentState == BluetoothProfile.STATE_CONNECTED) {
+ MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
+ }
+ if (DBG) {
+ Log.d(TAG, "Connection state " + mDevice + ": " + mMostRecentState + "->"
+ + currentState);
+ }
+ Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mMostRecentState = currentState;
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index 94658f6..d3dd731 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -23,12 +23,13 @@
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.android.bluetooth.R;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
import com.android.bluetooth.hfpclient.HeadsetClientService;
import java.util.List;
@@ -52,8 +53,8 @@
* restored.
*/
public class A2dpSinkStreamHandler extends Handler {
- private static final boolean DBG = false;
private static final String TAG = "A2dpSinkStreamHandler";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
// Configuration Variables
private static final int DEFAULT_DUCK_PERCENT = 25;
@@ -77,7 +78,7 @@
private static final int STATE_FOCUS_GRANTED = 1;
// Private variables.
- private A2dpSinkStateMachine mA2dpSinkSm;
+ private A2dpSinkService mA2dpSinkService;
private Context mContext;
private AudioManager mAudioManager;
// Keep track if the remote device is providing audio
@@ -86,6 +87,16 @@
// Keep track of the relevant audio focus (None, Transient, Gain)
private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+ // In order for Bluetooth to be considered as an audio source capable of receiving media key
+ // events (In the eyes of MediaSessionService), we need an active MediaPlayer in addition to a
+ // MediaSession. Because of this, the media player below plays an incredibly short, silent audio
+ // sample so that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the
+ // current active player and send the Bluetooth process media events. This allows AVRCP
+ // controller to create a MediaSession and handle the events if it would like. The player and
+ // session requirement is a restriction currently imposed by the media framework code and could
+ // be reconsidered in the future.
+ private MediaPlayer mMediaPlayer = null;
+
// Focus changes when we are currently holding focus.
private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
@Override
@@ -98,12 +109,26 @@
}
};
- public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
- mA2dpSinkSm = a2dpSinkSm;
+ public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context) {
+ mA2dpSinkService = a2dpSinkService;
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
+ void requestAudioFocus(boolean request) {
+ obtainMessage(REQUEST_FOCUS, request).sendToTarget();
+ }
+
+ int getFocusState() {
+ return mAudioFocus;
+ }
+
+ boolean isPlaying() {
+ return (mStreamAvailable
+ && (mAudioFocus == AudioManager.AUDIOFOCUS_GAIN
+ || mAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
+ }
+
@Override
public void handleMessage(Message message) {
if (DBG) {
@@ -112,13 +137,10 @@
}
switch (message.what) {
case SRC_STR_START:
+ mStreamAvailable = true;
if (isTvDevice() || shouldRequestFocus()) {
requestAudioFocusIfNone();
}
- // Audio stream has started, stop it if we don't have focus.
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- }
break;
case SRC_STR_STOP:
@@ -142,10 +164,6 @@
requestAudioFocusIfNone();
break;
}
- // Otherwise, pause if we don't have focus
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- }
break;
case SRC_PAUSE:
@@ -223,12 +241,17 @@
* Utility functions.
*/
private void requestAudioFocusIfNone() {
+ if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
requestAudioFocus();
}
+ // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
+ // This function immediately exits if we have a MediaPlayer object.
+ requestMediaKeyFocus();
}
private synchronized int requestAudioFocus() {
+ if (DBG) Log.d(TAG, "requestAudioFocus()");
// Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
// type unknown.
AudioAttributes streamAttributes =
@@ -252,89 +275,92 @@
return focusRequestStatus;
}
+ /**
+ * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
+ * aware of the fact that Bluetooth is playing audio.
+ *
+ * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
+ * chosen to use it.
+ */
+ private synchronized void requestMediaKeyFocus() {
+ if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
+
+ if (mMediaPlayer != null) return;
+
+ AudioAttributes attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+
+ mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+ mAudioManager.generateAudioSessionId());
+ if (mMediaPlayer == null) {
+ Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+ return;
+ }
+
+ mMediaPlayer.setLooping(false);
+ mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+ releaseMediaKeyFocus();
+ return false;
+ });
+
+ mMediaPlayer.start();
+ BluetoothMediaBrowserService.setActive(true);
+ }
private synchronized void abandonAudioFocus() {
+ if (DBG) Log.d(TAG, "abandonAudioFocus()");
stopFluorideStreaming();
+ releaseMediaKeyFocus();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
}
+ /**
+ * Destroys the silent audio sample MediaPlayer, notifying MediaSessionService of the fact
+ * we're no longer playing audio.
+ */
+ private synchronized void releaseMediaKeyFocus() {
+ if (DBG) Log.d(TAG, "releaseMediaKeyFocus()");
+ if (mMediaPlayer == null) {
+ return;
+ }
+ BluetoothMediaBrowserService.setActive(false);
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
private void startFluorideStreaming() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
- mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+ mA2dpSinkService.informAudioTrackGainNative(1.0f);
}
private void stopFluorideStreaming() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
private void setFluorideAudioTrackGain(float gain) {
- mA2dpSinkSm.informAudioTrackGainNative(gain);
+ mA2dpSinkService.informAudioTrackGainNative(gain);
}
private void sendAvrcpPause() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
- if (DBG) {
- Log.d(TAG, "sendAvrcpPause");
- }
- if (avrcpService != null) {
- List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
- if (!connectedDevices.isEmpty()) {
- BluetoothDevice targetDevice = connectedDevices.get(0);
- if (DBG) {
- Log.d(TAG, "Pausing AVRCP.");
- }
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
- } else {
- Log.e(TAG, "Passthrough not sent, connection un-available.");
- }
+ BluetoothMediaBrowserService.pause();
}
private void sendAvrcpPlay() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
- if (DBG) {
- Log.d(TAG, "sendAvrcpPlay");
- }
- if (avrcpService != null) {
- List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
- if (!connectedDevices.isEmpty()) {
- BluetoothDevice targetDevice = connectedDevices.get(0);
- if (DBG) {
- Log.d(TAG, "Playing AVRCP.");
- }
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
- } else {
- Log.e(TAG, "Passthrough not sent, connection un-available.");
- }
+ BluetoothMediaBrowserService.play();
}
private boolean inCallFromStreamingDevice() {
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
BluetoothDevice targetDevice = null;
- if (avrcpService != null) {
- List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
- if (!connectedDevices.isEmpty()) {
- targetDevice = connectedDevices.get(0);
- }
+ List<BluetoothDevice> connectedDevices = mA2dpSinkService.getConnectedDevices();
+ if (!connectedDevices.isEmpty()) {
+ targetDevice = connectedDevices.get(0);
}
HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
- if (targetDevice != null && headsetClientService != null) {
+ if (targetDevice != null && headsetClientService != null) {
return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
}
return false;
@@ -354,7 +380,7 @@
private boolean shouldRequestFocus() {
return mContext.getResources()
- .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
+ .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
}
}
diff --git a/src/com/android/bluetooth/a2dpsink/StackEvent.java b/src/com/android/bluetooth/a2dpsink/StackEvent.java
new file mode 100644
index 0000000..68a6ce9
--- /dev/null
+++ b/src/com/android/bluetooth/a2dpsink/StackEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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.a2dpsink;
+
+import android.bluetooth.BluetoothDevice;
+
+final class StackEvent {
+ // Event types for STACK_EVENT message
+ static final int EVENT_TYPE_NONE = 0;
+ static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+ static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+
+ // match up with btav_connection_state_t enum of bt_av.h
+ static final int CONNECTION_STATE_DISCONNECTED = 0;
+ static final int CONNECTION_STATE_CONNECTING = 1;
+ static final int CONNECTION_STATE_CONNECTED = 2;
+ static final int CONNECTION_STATE_DISCONNECTING = 3;
+
+ // match up with btav_audio_state_t enum of bt_av.h
+ static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
+ static final int AUDIO_STATE_STOPPED = 1;
+ static final int AUDIO_STATE_STARTED = 2;
+
+ int mType = EVENT_TYPE_NONE;
+ BluetoothDevice mDevice = null;
+ int mState = 0;
+ int mSampleRate = 0;
+ int mChannelCount = 0;
+
+ private StackEvent(int type) {
+ this.mType = type;
+ }
+
+ @Override
+ public String toString() {
+ switch (mType) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mState;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ return "EVENT_TYPE_AUDIO_STATE_CHANGED " + mState;
+ case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ return "EVENT_TYPE_AUDIO_CONFIG_CHANGED " + mSampleRate + ":" + mChannelCount;
+ default:
+ return "Unknown";
+ }
+ }
+
+ static StackEvent connectionStateChanged(BluetoothDevice device, int state) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.mDevice = device;
+ event.mState = state;
+ return event;
+ }
+
+ static StackEvent audioStateChanged(BluetoothDevice device, int state) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.mDevice = device;
+ event.mState = state;
+ return event;
+ }
+
+ static StackEvent audioConfigChanged(BluetoothDevice device, int sampleRate,
+ int channelCount) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+ event.mDevice = device;
+ event.mSampleRate = sampleRate;
+ event.mChannelCount = channelCount;
+ return event;
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index f6ce92f..52178f1 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -21,14 +21,11 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Intent;
import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.PlaybackState;
import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Message;
import android.util.Log;
import com.android.bluetooth.Utils;
@@ -37,15 +34,21 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
/**
* 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 = false;
- static final boolean VDBG = false;
+ static final int MAXIMUM_CONNECTED_DEVICES = 5;
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+ public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
/*
* Play State Values from JNI
*/
@@ -56,97 +59,19 @@
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
+ /* Folder/Media Item scopes.
+ * Keep in sync with AVRCP 1.6 sec. 6.10.1
*/
- 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;
+ public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00;
+ public static final byte BROWSE_SCOPE_VFS = 0x01;
+ public static final byte BROWSE_SCOPE_SEARCH = 0x02;
+ public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03;
- /*
- * Browsing folder types
- * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
+ /* Folder navigation directions
+ * This is borrowed from AVRCP 1.6 spec and must be kept with same values
*/
- 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;
-
- /*
- * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t.
- * NOTE: Not all may be defined.
- */
- private static final int JNI_AVRC_STS_NO_ERROR = 0x04;
- private static final int JNI_AVRC_INV_RANGE = 0x0b;
-
- /**
- * 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";
+ public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+ public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
/*
* KeyCoded for Pass Through Commands
@@ -165,293 +90,109 @@
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;
+ static BrowseTree sBrowseTree;
+ private static AvrcpControllerService sService;
+ private final BluetoothAdapter mAdapter;
- /* Folder navigation directions
- * This is borrowed from AVRCP 1.6 spec and must be kept with same values
- */
- public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
- public static final byte 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;
+ protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
+ new ConcurrentHashMap<>(1);
static {
classInitNative();
}
public AvrcpControllerService() {
- initNative();
- }
-
- @Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothAvrcpControllerBinder(this);
+ super();
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
protected boolean start() {
- HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
- thread.start();
- mAvrcpCtSm = new AvrcpControllerStateMachine(this);
- mAvrcpCtSm.start();
+ initNative();
+ sBrowseTree = new BrowseTree(null);
+ sService = this;
- setAvrcpControllerService(this);
+ // Start the media browser service.
+ Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ startService(startIntent);
return true;
}
@Override
protected boolean stop() {
- setAvrcpControllerService(null);
- if (mAvrcpCtSm != null) {
- mAvrcpCtSm.doQuit();
+ Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ stopService(stopIntent);
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ stateMachine.quitNow();
}
+
+ sService = null;
+ sBrowseTree = null;
return true;
}
- //API Methods
-
- public static synchronized AvrcpControllerService getAvrcpControllerService() {
- if (sAvrcpControllerService == null) {
- Log.w(TAG, "getAvrcpControllerService(): service is null");
- return null;
- }
- if (!sAvrcpControllerService.isAvailable()) {
- Log.w(TAG, "getAvrcpControllerService(): service is not available ");
- return null;
- }
- return sAvrcpControllerService;
+ public static AvrcpControllerService getAvrcpControllerService() {
+ return sService;
}
- private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
- if (DBG) {
- Log.d(TAG, "setAvrcpControllerService(): set to: " + instance);
- }
- sAvrcpControllerService = instance;
+ protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
+ return new AvrcpControllerStateMachine(device, this);
}
- public synchronized List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- if (mConnectedDevice != null) {
- devices.add(mConnectedDevice);
+ private void refreshContents(BrowseTree.BrowseNode node) {
+ if (node.mDevice == null) {
+ return;
}
- return devices;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(node.mDevice);
+ if (stateMachine != null) {
+ stateMachine.requestContents(node);
+ }
}
+ /*Java API*/
+
/**
- * This function only supports STATE_CONNECTED
+ * Get a List of MediaItems that are children of the specified media Id
+ *
+ * @param parentMediaId The player or folder to get the contents of
+ * @return List of Children if available, an empty list if there are none,
+ * or null if a search must be performed.
*/
- 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);
+ public synchronized List<MediaItem> getContents(String parentMediaId) {
+ if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");
+
+ BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
+ if (requestedNode == null) {
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ requestedNode = stateMachine.findNode(parentMediaId);
+ if (requestedNode != null) {
+ Log.d(TAG, "Found a node");
+ break;
+ }
}
}
- 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 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");
+ if (requestedNode == null) {
+ if (DBG) Log.d(TAG, "Didn't find a node");
return null;
+ } else {
+ if (!requestedNode.isCached()) {
+ if (DBG) Log.d(TAG, "node is not cached");
+ refreshContents(requestedNode);
+ }
+ if (DBG) Log.d(TAG, "Returning contents");
+ return requestedNode.getContents();
}
-
- if (!device.equals(mConnectedDevice)) {
- return null;
- }
- return mAvrcpCtSm.getCurrentMetaData();
}
- public PlaybackState getPlaybackState(BluetoothDevice device) {
- // Get the cached state by default.
- return getPlaybackState(device, true);
- }
-
- // cached can be used to force a getPlaybackState command. Useful for PTS testing.
- public synchronized PlaybackState getPlaybackState(BluetoothDevice device, boolean cached) {
- 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(cached);
- }
-
- 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;
- }
-
- /**
- * Retreive the contents of the directory from the associated bluetooth device.
- */
- public synchronized List<MediaItem> getContents(BluetoothDevice device, String parentMediaId) {
- return mAvrcpCtSm.getContents(parentMediaId);
- }
-/*
- public synchronized boolean 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 false;
- }
-
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getFolderListing browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
- items, id);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-*/
- 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);
+ @Override
+ protected IProfileServiceBinder initBinder() {
+ return new AvrcpControllerServiceBinder(this);
}
//Binder object: Must be static class or memory leak may occur
- private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+ private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
-
private AvrcpControllerService mService;
private AvrcpControllerService getService() {
@@ -460,14 +201,14 @@
return null;
}
- if (mService != null && mService.isAvailable()) {
+ if (mService != null) {
return mService;
}
return null;
}
- BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
- mService = svc;
+ AvrcpControllerServiceBinder(AvrcpControllerService service) {
+ mService = service;
}
@Override
@@ -499,153 +240,102 @@
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;
- }
+ Log.w(TAG, "sendGroupNavigationCmd not implemented");
+ return;
+ }
- if (device == null) {
- throw new IllegalStateException("Device cannot be null!");
- }
-
- service.sendGroupNavigationCmd(device, keyCode, keyState);
+ @Override
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) {
+ Log.w(TAG, "setPlayerApplicationSetting not implemented");
+ return false;
}
@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);
+ Log.w(TAG, "getPlayerSettings not implemented");
+ return null;
}
}
+
+ /* JNI API*/
// Called by JNI when a passthrough key was received.
private void handlePassthroughRsp(int id, int keyState, byte[] address) {
- Log.d(TAG,
- "passthrough response received as: key: " + id + " state: " + keyState + "address:"
- + address);
+ if (DBG) {
+ Log.d(TAG, "passthrough response received as: key: " + id
+ + " state: " + keyState + "address:" + address);
+ }
}
private void handleGroupNavigationRsp(int id, int keyState) {
- Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState);
+ if (DBG) {
+ 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 rcConnected, boolean brConnected,
- byte[] address) {
+ private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
+ boolean browsingConnected, byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Log.d(TAG, "onConnectionStateChanged " + rcConnected + " " + brConnected + device
- + " conn device " + mConnectedDevice);
+ if (DBG) {
+ Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
+ + browsingConnected + device);
+ }
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 = (rcConnected ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
-
- if (rcConnected && 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 (!rcConnected && 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 (rcConnected && brConnected) {
- mBrowseConnected = true;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
- msg.arg1 = 1;
- msg.obj = device;
- mAvrcpCtSm.sendMessage(msg);
+ StackEvent event =
+ StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
+ AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
+ if (remoteControlConnected || browsingConnected) {
+ stateMachine.connect(event);
+ } else {
+ stateMachine.disconnect();
}
}
// 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);
+ /* Do Nothing. */
}
// Called by JNI
private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
- /* Do Nothing. */
+ /* 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;
+ if (DBG) {
+ Log.d(TAG, "handleRegisterNotificationAbsVol");
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
- (int) label, 0);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
+ }
}
// 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;
+ if (DBG) {
+ Log.d(TAG, "handleSetAbsVolume ");
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
+ absVol);
+ }
}
// Called by JNI when a track changes and local AvrcpController is registered for updates.
@@ -654,24 +344,13 @@
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);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
+ TrackInfo.getMetadata(attributes, attribVals));
}
- List<String> attrValList = Arrays.asList(attribVals);
- TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
- if (VDBG) {
- 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
@@ -681,14 +360,12 @@
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;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
+ songLen, currSongPosition);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
- songLen, currSongPosition);
- mAvrcpCtSm.sendMessage(msg);
}
// Called by JNI on changes of play status
@@ -696,11 +373,6 @@
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:
@@ -721,9 +393,12 @@
default:
playbackState = PlaybackState.STATE_NONE;
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+ }
}
// Called by JNI to report remote Player's capabilities
@@ -733,13 +408,13 @@
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;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ PlayerApplicationSettings supportedSettings =
+ PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
}
- PlayerApplicationSettings supportedSettings =
- PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
/* Do nothing */
+
}
private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
@@ -748,50 +423,48 @@
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;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+
+ PlayerApplicationSettings desiredSettings =
+ PlayerApplicationSettings.makeSettings(playerAttribRsp);
}
- PlayerApplicationSettings desiredSettings =
- PlayerApplicationSettings.makeSettings(playerAttribRsp);
/* Do nothing */
}
// Browsing related JNI callbacks.
- void handleGetFolderItemsRsp(int status, MediaItem[] items) {
+ void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
+ items.length + " items.");
}
-
- if (status == JNI_AVRC_INV_RANGE) {
- Log.w(TAG, "Sending out of range message.");
- // Send a special message since this could be used by state machine
- // to take as a signal that fetch is finished.
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
- mAvrcpCtSm.sendMessage(msg);
- return;
- }
-
for (MediaItem item : items) {
if (VDBG) {
- Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
+ 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);
+
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
+ itemsList);
+ }
}
- void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
+
+ void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
}
+
for (AvrcpPlayer item : items) {
if (VDBG) {
Log.d(TAG, "bt player item: " + item);
@@ -802,28 +475,31 @@
itemsList.add(p);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+ itemsList);
+ }
}
// JNI Helper functions to convert native objects to java.
- MediaItem createFromNativeMediaItem(byte[] uid, int type, String name, int[] attrIds,
+ MediaItem createFromNativeMediaItem(long uid, int type, String name, int[] attrIds,
String[] attrVals) {
if (VDBG) {
- Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " + name
- + " attrids " + attrIds + " attrVals " + attrVals);
+ 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));
+ mdExtra.putLong(MEDIA_ITEM_UID_KEY, 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);
@@ -833,23 +509,20 @@
return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
}
- MediaItem createFromNativeFolderItem(byte[] uid, int type, String name, int playable) {
+ MediaItem createFromNativeFolderItem(long uid, int type, String name, int playable) {
if (VDBG) {
- Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " + name
- + " playable " + playable);
+ 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));
+ mdExtra.putLong(MEDIA_ITEM_UID_KEY, 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);
@@ -860,90 +533,172 @@
int playStatus, int playerType) {
if (VDBG) {
Log.d(TAG,
- "createFromNativePlayerItem name: " + name + " transportFlags " + transportFlags
- + " play status " + playStatus + " player type " + playerType);
+ "createFromNativePlayerItem name: " + name + " transportFlags "
+ + transportFlags + " play status " + playStatus + " player type "
+ + playerType);
}
AvrcpPlayer player = new AvrcpPlayer(id, name, transportFlags, playStatus, playerType);
return player;
}
- private void handleChangeFolderRsp(int count) {
+ private void handleChangeFolderRsp(byte[] address, int count) {
if (DBG) {
Log.d(TAG, "handleChangeFolderRsp count: " + count);
}
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
- count);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
+ count);
+ }
}
- private void handleSetBrowsedPlayerRsp(int items, int depth) {
+ private void handleSetBrowsedPlayerRsp(byte[] address, 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);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER,
+ items, depth);
+ }
}
- private void handleSetAddressedPlayerRsp(int status) {
+ private void handleSetAddressedPlayerRsp(byte[] address, int status) {
if (DBG) {
Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
+ }
}
- private void handleAddressedPlayerChanged(int id) {
+ private void handleAddressedPlayerChanged(byte[] address, int id) {
if (DBG) {
Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
+ }
}
- private void handleNowPlayingContentChanged() {
+ private void handleNowPlayingContentChanged(byte[] address) {
if (DBG) {
Log.d(TAG, "handleNowPlayingContentChanged");
}
- mAvrcpCtSm.sendMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.nowPlayingContentChanged();
+ }
+ }
+
+ /* Generic Profile Code */
+
+ /**
+ * Disconnect the given Bluetooth device.
+ *
+ * @return true if disconnect is successful, false otherwise.
+ */
+ public synchronized boolean disconnect(BluetoothDevice device) {
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "MAP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a map state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return false;
+ }
+ int connectionState = stateMachine.getState();
+ if (connectionState != BluetoothProfile.STATE_CONNECTED
+ && connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ stateMachine.disconnect();
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "MAP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ return true;
+ }
+
+ void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
+ mDeviceStateMap.remove(stateMachine.getDevice());
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+ }
+
+ protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
+ return mDeviceStateMap.get(device);
+ }
+
+ protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
+ stateMachine = newStateMachine(device);
+ mDeviceStateMap.put(device, stateMachine);
+ stateMachine.start();
+ }
+ return stateMachine;
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+ List<BluetoothDevice> deviceList = new ArrayList<>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ for (BluetoothDevice device : bondedDevices) {
+ connectionState = getConnectionState(device);
+ if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+ for (int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ if (DBG) Log.d(TAG, deviceList.toString());
+ Log.d(TAG, "GetDevicesDone");
+ return deviceList;
+ }
+
+ synchronized int getConnectionState(BluetoothDevice device) {
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+ : stateMachine.getState();
}
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- mAvrcpCtSm.dump(sb);
+ ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ ProfileService.println(sb,
+ "==== StateMachine for " + stateMachine.getDevice() + " ====");
+ stateMachine.dump(sb);
+ }
+ sb.append("\n sBrowseTree: " + sBrowseTree.toString());
}
- 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;
- }
-
+ /*JNI*/
private static native void classInitNative();
private native void initNative();
@@ -978,9 +733,9 @@
static native void getPlayerListNative(byte[] address, int start, int end);
/* API used to change the folder */
- static native void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+ static native void changeFolderPathNative(byte[] address, byte direction, long uid);
- static native void playItemNative(byte[] address, byte scope, byte[] uid, int uidCounter);
+ static native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
static native void setBrowsedPlayerNative(byte[] address, int playerId);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index f13f0c4..13ee58d 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -19,19 +19,20 @@
import android.bluetooth.BluetoothAvrcpController;
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.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.MetricsLogger;
@@ -41,383 +42,375 @@
import java.util.ArrayList;
import java.util.List;
-
/**
* Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
* and interactions with a remote controlable device.
*/
class AvrcpControllerStateMachine extends StateMachine {
+ static final String TAG = "AvrcpControllerStateMachine";
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
- // 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_FOLDER_LIST = 6;
- static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9;
+ //0->99 Events from Outside
+ public static final int CONNECT = 1;
+ public static final int DISCONNECT = 2;
- // 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_FOLDER_ITEMS_OUT_OF_RANGE = 110;
- static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111;
- static final int MESSAGE_PROCESS_FOLDER_PATH = 112;
- static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113;
- static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 114;
- static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 115;
- static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 116;
+ //100->199 Internal Events
+ protected static final int CLEANUP = 100;
+ private static final int CONNECT_TIMEOUT = 101;
- // 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;
+ //200->299 Events from Native
+ static final int STACK_EVENT = 200;
+ static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201;
- // Interal messages
- static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403;
+ static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203;
+ static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204;
+ static final int MESSAGE_PROCESS_TRACK_CHANGED = 205;
+ static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206;
+ static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207;
+ static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208;
+ static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209;
+ static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210;
+ static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211;
+ static final int MESSAGE_PROCESS_FOLDER_PATH = 212;
+ static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213;
+ static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
+ static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
+ static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
+
+ //300->399 Events for Browsing
+ static final int MESSAGE_GET_FOLDER_ITEMS = 300;
+ static final int MESSAGE_PLAY_ITEM = 301;
+ static final int MSG_AVRCP_PASSTHRU = 302;
+
static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
- static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
- static final int CMD_TIMEOUT_MILLIS = 5000; // 5s
- // Fetch only 20 items at a time.
- static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 20;
- // Fetch no more than 1000 items per directory.
- static final int MAX_FOLDER_ITEMS = 1000;
-
/*
* 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 = true;
- private static final boolean VDBG = false;
-
- private final Context mContext;
private final AudioManager mAudioManager;
- private final State mDisconnected;
- private final State mConnected;
- private final SetAddresedPlayerAndPlayItem mSetAddrPlayer;
- private final GetFolderList mGetFolderList;
+ protected final BluetoothDevice mDevice;
+ protected final byte[] mDeviceAddress;
+ protected final AvrcpControllerService mService;
+ protected final Disconnected mDisconnected;
+ protected final Connecting mConnecting;
+ protected final Connected mConnected;
+ protected final Disconnecting mDisconnecting;
- private final Object mLock = new Object();
- private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build();
+ protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
- // 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 int mVolumeChangedNotificationsToIgnore = 0;
- private int mPreviousPercentageVol = -1;
- private int mAddressedPlayerID = -1;
+ boolean mRemoteControlConnected = false;
+ boolean mBrowsingConnected = false;
+ BrowseTree mBrowseTree = null;
+ private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
+ private int mVolumeChangedNotificationsToIgnore = 0;
- // Browse tree.
- private BrowseTree mBrowseTree = new BrowseTree();
+ GetFolderList mGetFolderList = null;
- AvrcpControllerStateMachine(Context context) {
+ //Number of items to get in a single fetch
+ static final int ITEM_PAGE_SIZE = 20;
+ static final int CMD_TIMEOUT_MILLIS = 10000;
+ static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
+
+ AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
super(TAG);
- mContext = context;
-
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mDevice = device;
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ logD(device.toString());
mDisconnected = new Disconnected();
+ mConnecting = new Connecting();
mConnected = new Connected();
-
- // Used to change folder path and fetch the new folder listing.
- mSetAddrPlayer = new SetAddresedPlayerAndPlayItem();
- mGetFolderList = new GetFolderList();
+ mDisconnecting = new Disconnecting();
addState(mDisconnected);
+ addState(mConnecting);
addState(mConnected);
+ addState(mDisconnecting);
- // 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(mSetAddrPlayer, mConnected);
+ mGetFolderList = new GetFolderList();
addState(mGetFolderList, mConnected);
+ mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
setInitialState(mDisconnected);
}
- class Disconnected extends State {
+ BrowseTree.BrowseNode findNode(String parentMediaId) {
+ logD("FindNode");
+ return mBrowseTree.findBrowseNodeByID(parentMediaId);
+ }
+
+ /**
+ * Get the current connection state
+ *
+ * @return current State
+ */
+ public int getState() {
+ return mMostRecentState;
+ }
+
+ /**
+ * Get the underlying device tracked by this state machine
+ *
+ * @return device in focus
+ */
+ public synchronized BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * send the connection event asynchronously
+ */
+ public boolean connect(StackEvent event) {
+ sendMessage(CONNECT);
+ if (!mBrowsingConnected && event.mBrowsingConnected) {
+ onBrowsingConnected();
+ }
+ mRemoteControlConnected = event.mRemoteControlConnected;
+ mBrowsingConnected = event.mBrowsingConnected;
+ return true;
+ }
+
+ /**
+ * send the Disconnect command asynchronously
+ */
+ public void disconnect() {
+ sendMessage(DISCONNECT);
+ }
+
+ /**
+ * Dump the current State Machine to the string builder.
+ *
+ * @param sb output string
+ */
+ public void dump(StringBuilder sb) {
+ ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
+ }
+
+ @Override
+ protected void unhandledMessage(Message msg) {
+ Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what);
+ }
+
+ private static void logD(String message) {
+ if (DBG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ void onBrowsingConnected() {
+ mBrowseTree = new BrowseTree(mDevice);
+ mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mService
+ .sBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ }
+
+ void onBrowsingDisconnected() {
+ mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
+ mAddressedPlayer.updateCurrentTrack(null);
+ mBrowseTree.mNowPlayingNode.setCached(false);
+ BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
+ PlaybackState.Builder pbb = new PlaybackState.Builder();
+ pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ 1.0f).setActions(0);
+ BluetoothMediaBrowserService.notifyChanged(pbb.build());
+ mService.sBrowseTree.mRootNode.removeChild(
+ mBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mService
+ .sBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.trackChanged(null);
+
+ }
+
+ private void notifyChanged(BrowseTree.BrowseNode node) {
+ BluetoothMediaBrowserService.notifyChanged(node);
+ }
+
+ void requestContents(BrowseTree.BrowseNode node) {
+ sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);
+
+ logD("Fetching " + node);
+ }
+
+ void nowPlayingContentChanged() {
+ mBrowseTree.mNowPlayingNode.setCached(false);
+ sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
+ }
+
+ protected class Disconnected extends State {
+ @Override
+ public void enter() {
+ logD("Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
+ }
@Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
- switch (msg.what) {
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
- mBrowseTree = new BrowseTree();
- transitionTo(mConnected);
- BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
- synchronized (mLock) {
- mRemoteDevice = new RemoteDevice(rtDevice);
- mAddressedPlayer = new AvrcpPlayer();
- mIsConnected = true;
- }
- MetricsLogger.logProfileConnectionEvent(
- BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
- 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);
- }
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CONNECT:
+ logD("Connect");
+ transitionTo(mConnecting);
break;
-
- default:
- Log.w(TAG,
- "Currently Disconnected not handling " + dumpMessageString(msg.what));
- return false;
+ case CLEANUP:
+ mService.removeStateMachine(AvrcpControllerStateMachine.this);
+ break;
}
return true;
}
}
+ protected class Connecting extends State {
+ @Override
+ public void enter() {
+ logD("Enter Connecting");
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+ transitionTo(mConnected);
+ }
+ }
+
+
class Connected extends State {
+ private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController";
+ private int mCurrentlyHeldKey = 0;
+
+ @Override
+ public void enter() {
+ if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+ BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+ } else {
+ logD("ReEnteringConnected");
+ }
+ super.enter();
+ }
+
@Override
public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
- A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
- synchronized (mLock) {
- switch (msg.what) {
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- BluetoothDevice device = (BluetoothDevice) msg.obj;
- AvrcpControllerService.sendPassThroughCommandNative(
- Utils.getByteAddress(device), msg.arg1, msg.arg2);
- if (a2dpSinkService != null) {
- if (DBG) Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
- a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
- }
- break;
+ logD(STATE_TAG + " processMessage " + msg.what);
+ switch (msg.what) {
+ case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ mVolumeChangedNotificationsToIgnore++;
+ removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+ sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+ ABS_VOL_TIMEOUT_MILLIS);
+ setAbsVolume(msg.arg1, msg.arg2);
+ return true;
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- AvrcpControllerService.sendGroupNavigationCommandNative(
- mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
- break;
+ case MESSAGE_GET_FOLDER_ITEMS:
+ transitionTo(mGetFolderList);
+ return true;
- case MESSAGE_GET_FOLDER_LIST:
- // Whenever we transition we set the information for folder we need to
- // return result.
- if (DBG) Log.d(TAG, "Message_GET_FOLDER_LIST" + (String) msg.obj);
- mGetFolderList.setFolder((String) msg.obj);
- transitionTo(mGetFolderList);
- break;
+ case MESSAGE_PLAY_ITEM:
+ //Set Addressed Player
+ playItem((BrowseTree.BrowseNode) msg.obj);
+ return true;
- case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
- int scope = msg.arg1;
- String playItemUid = (String) msg.obj;
- BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer();
- BrowseTree.BrowseNode currAddrPlayer =
- mBrowseTree.getCurrentAddressedPlayer();
- BrowseTree.BrowseNode itemToPlay =
- mBrowseTree.findBrowseNodeByID(playItemUid);
- if (DBG) {
- Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
- + currAddrPlayer);
- }
- if (currBrPlayer == null
- || currBrPlayer.equals(currAddrPlayer)
- || scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
- // String is encoded as a Hex String (mostly for display purposes)
- // hence convert this back to real byte string.
- // NOTE: It may be possible that sending play while the same item is
- // playing leads to reset of track.
- AvrcpControllerService.playItemNative(
- mRemoteDevice.getBluetoothAddress(), (byte) scope,
- AvrcpControllerService.hexStringToByteUID(playItemUid), 0);
- } else {
- // Send out the request for setting addressed player.
- AvrcpControllerService.setAddressedPlayerNative(
- mRemoteDevice.getBluetoothAddress(),
- currBrPlayer.getPlayerID());
- mSetAddrPlayer.setItemAndScope(currBrPlayer.getID(), playItemUid,
- scope);
- transitionTo(mSetAddrPlayer);
- }
- break;
+ case MSG_AVRCP_PASSTHRU:
+ passThru(msg.arg1);
+ return true;
+
+ case MESSAGE_PROCESS_TRACK_CHANGED:
+ mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
+ BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
+ return true;
+
+ case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+ mAddressedPlayer.setPlayStatus(msg.arg1);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ if (mAddressedPlayer.getPlaybackState().getState()
+ == PlaybackState.STATE_PLAYING
+ && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
+ && !shouldRequestFocus()) {
+ sendMessage(MSG_AVRCP_PASSTHRU,
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
}
+ return true;
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
- synchronized (mLock) {
- mIsConnected = false;
- mRemoteDevice = null;
- }
- mBrowseTree.clear();
- 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_PLAY_POS_CHANGED:
+ if (msg.arg2 != -1) {
+ mAddressedPlayer.setPlayTime(msg.arg2);
- 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);
- } 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:
- mVolumeChangedNotificationsToIgnore++;
- removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
- sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
- ABS_VOL_TIMEOUT_MILLIS);
- setAbsVolume(msg.arg1, msg.arg2);
- break;
-
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
- mRemoteDevice.setNotificationLabel(msg.arg1);
- mRemoteDevice.setAbsVolNotificationRequested(true);
- int percentageVol = getVolumePercentage();
- if (DBG) {
- Log.d(TAG, " Sending Interim Response = " + percentageVol + " label "
- + msg.arg1);
- }
- AvrcpControllerService.sendRegisterAbsVolRspNative(
- mRemoteDevice.getBluetoothAddress(), NOTIFICATION_RSP_TYPE_INTERIM,
- percentageVol, mRemoteDevice.getNotificationLabel());
+ BluetoothMediaBrowserService.notifyChanged(
+ mAddressedPlayer.getPlaybackState());
}
- break;
+ return true;
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
- if (mVolumeChangedNotificationsToIgnore > 0) {
- mVolumeChangedNotificationsToIgnore--;
- if (mVolumeChangedNotificationsToIgnore == 0) {
- removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
- }
- } else {
- if (mRemoteDevice.getAbsVolNotificationRequested()) {
- int percentageVol = getVolumePercentage();
- if (percentageVol != mPreviousPercentageVol) {
- AvrcpControllerService.sendRegisterAbsVolRspNative(
- mRemoteDevice.getBluetoothAddress(),
- NOTIFICATION_RSP_TYPE_CHANGED, percentageVol,
- mRemoteDevice.getNotificationLabel());
- mPreviousPercentageVol = percentageVol;
- mRemoteDevice.setAbsVolNotificationRequested(false);
- }
- }
- }
- }
- break;
+ case DISCONNECT:
+ transitionTo(mDisconnecting);
+ return true;
- case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT:
- // Volume changed notifications should come back promptly from the
- // AudioManager, if for some reason some notifications were squashed don't
- // prevent future notifications.
- if (DBG) Log.d(TAG, "Timed out on volume changed notification");
- mVolumeChangedNotificationsToIgnore = 0;
- break;
+ default:
+ return super.processMessage(msg);
+ }
- case MESSAGE_PROCESS_TRACK_CHANGED:
- // Music start playing automatically and update Metadata
- boolean updateTrack =
- mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
- if (updateTrack) {
- broadcastMetaDataChanged(
- mAddressedPlayer.getCurrentTrack().getMediaMetaData());
- }
- break;
+ }
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- if (msg.arg2 != -1) {
- mAddressedPlayer.setPlayTime(msg.arg2);
- broadcastPlayBackStateChanged(getCurrentPlayBackState());
- }
- break;
+ private void playItem(BrowseTree.BrowseNode node) {
+ if (node == null) {
+ Log.w(TAG, "Invalid item to play");
+ } else {
+ mService.playItemNative(
+ mDeviceAddress, node.getScope(),
+ node.getBluetoothID(), 0);
+ }
+ }
- 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;
+ private synchronized void passThru(int cmd) {
+ logD("msgPassThru " + cmd);
+ // Some keys should be held until the next event.
+ if (mCurrentlyHeldKey != 0) {
+ mService.sendPassThroughCommandNative(
+ mDeviceAddress, mCurrentlyHeldKey,
+ AvrcpControllerService.KEY_STATE_RELEASED);
- case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
- mAddressedPlayerID = msg.arg1;
- if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayerID);
- AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerID);
- if (updatedPlayer != null) {
- mAddressedPlayer = updatedPlayer;
- if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayer.getName());
- } else {
- mBrowseTree.mRootNode.setCached(false);
- }
- sendMessage(MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
- break;
-
- case MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED:
- mBrowseTree.mNowPlayingNode.setCached(false);
- mGetFolderList.setFolder(mBrowseTree.mNowPlayingNode.getID());
- transitionTo(mGetFolderList);
- break;
-
- default:
- Log.d(TAG, "Unhandled message" + msg.what);
- return false;
+ if (mCurrentlyHeldKey == cmd) {
+ // Return to prevent starting FF/FR operation again
+ mCurrentlyHeldKey = 0;
+ return;
+ } else {
+ // FF/FR is in progress and other operation is desired
+ // so after stopping FF/FR, not returning so that command
+ // can be sent for the desired operation.
+ mCurrentlyHeldKey = 0;
}
}
- return true;
+
+ // Send the pass through.
+ mService.sendPassThroughCommandNative(mDeviceAddress, cmd,
+ AvrcpControllerService.KEY_STATE_PRESSED);
+
+ if (isHoldableKey(cmd)) {
+ // Release cmd next time a command is sent.
+ mCurrentlyHeldKey = cmd;
+ } else {
+ mService.sendPassThroughCommandNative(mDeviceAddress,
+ cmd, AvrcpControllerService.KEY_STATE_RELEASED);
+ }
}
+
+ private boolean isHoldableKey(int cmd) {
+ return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
+ || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+ }
+
+
}
+
// Handle the get folder listing action
// a) Fetch the listing of folders
// b) Once completed return the object listing
- class GetFolderList extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.GetFolderList";
+ class GetFolderList extends State {
+ private static final String STATE_TAG = "Avrcp.GetFolderList";
boolean mAbort;
BrowseTree.BrowseNode mBrowseNode;
@@ -425,9 +418,19 @@
@Override
public void enter() {
+ logD(STATE_TAG + " Entering GetFolderList");
// Setup the timeouts.
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
super.enter();
mAbort = false;
+ Message msg = getCurrentMessage();
+ if (msg.what == MESSAGE_GET_FOLDER_ITEMS) {
+ {
+ logD(STATE_TAG + " new Get Request");
+ mBrowseNode = (BrowseTree.BrowseNode) msg.obj;
+ }
+ }
+
if (mBrowseNode == null) {
transitionTo(mConnected);
} else {
@@ -435,28 +438,20 @@
}
}
- public void setFolder(String id) {
- if (DBG) Log.d(STATE_TAG, "Setting folder to " + id);
- mBrowseNode = mBrowseTree.findBrowseNodeByID(id);
- }
-
@Override
public boolean processMessage(Message msg) {
- Log.d(STATE_TAG, "processMessage " + msg.what);
+ logD(STATE_TAG + " processMessage " + msg.what);
switch (msg.what) {
case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
int endIndicator = mBrowseNode.getExpectedChildren() - 1;
- if (DBG) {
- Log.d(STATE_TAG,
- " End " + endIndicator
- + " received " + folderList.size());
- }
+ logD("GetFolderItems: End " + endIndicator
+ + " received " + folderList.size());
// Always update the node so that the user does not wait forever
// for the list to populate.
mBrowseNode.addChildren(folderList);
- broadcastFolderList(mBrowseNode.getID());
+ notifyChanged(mBrowseNode);
if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
|| mAbort) {
@@ -505,16 +500,15 @@
mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
rootNode.setExpectedChildren(playerList.size());
rootNode.setCached(true);
- broadcastFolderList(BrowseTree.ROOT);
+ notifyChanged(rootNode);
}
transitionTo(mConnected);
break;
-
case MESSAGE_INTERNAL_CMD_TIMEOUT:
// We have timed out to execute the request, we should simply send
// whatever listing we have gotten until now.
- broadcastFolderList(mBrowseNode.getID());
+ Log.w(TAG, "TIMEOUT");
transitionTo(mConnected);
break;
@@ -523,41 +517,25 @@
// already sent all the items to the client hence simply
// transition to Connected state here.
mBrowseNode.setCached(true);
- broadcastFolderList(mBrowseNode.getID());
transitionTo(mConnected);
break;
- case MESSAGE_GET_FOLDER_LIST:
- if (!mBrowseNode.equals((String) msg.obj)) {
- mAbort = true;
+ case MESSAGE_GET_FOLDER_ITEMS:
+ if (!mBrowseNode.equals(msg.obj)) {
+ if (mBrowseNode.getScope()
+ == ((BrowseTree.BrowseNode) msg.obj).getScope()) {
+ mAbort = true;
+ }
deferMessage(msg);
- Log.d(STATE_TAG, "Go Get Another Directory");
+ logD("GetFolderItems: Go Get Another Directory");
} else {
- Log.d(STATE_TAG, "Get The Same Directory, ignore");
+ logD("GetFolderItems: Get The Same Directory, ignore");
}
break;
- case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM:
- // A new request has come in, no need to fetch more.
- mAbort = true;
- deferMessage(msg);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
+ logD(STATE_TAG + " deferring message " + msg.what
+ + " to connected!");
deferMessage(msg);
}
return true;
@@ -566,22 +544,23 @@
private void fetchContents(BrowseTree.BrowseNode target) {
int start = target.getChildrenCount();
int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
- + GET_FOLDER_ITEMS_PAGINATION_SIZE) - 1;
+ + ITEM_PAGE_SIZE) - 1;
switch (target.getScope()) {
case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
- AvrcpControllerService.getPlayerListNative(mRemoteDevice.getBluetoothAddress(),
+ AvrcpControllerService.getPlayerListNative(mDeviceAddress,
start, end);
break;
case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
AvrcpControllerService.getNowPlayingListNative(
- mRemoteDevice.getBluetoothAddress(), start, end);
+ mDeviceAddress, start, end);
break;
case AvrcpControllerService.BROWSE_SCOPE_VFS:
- AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
+ AvrcpControllerService.getFolderListNative(mDeviceAddress,
start, end);
break;
default:
- Log.e(STATE_TAG, "Scope " + target.getScope() + " cannot be handled here.");
+ Log.e(TAG, STATE_TAG + " Scope " + target.getScope()
+ + " cannot be handled here.");
}
}
@@ -598,323 +577,172 @@
*/
private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
mNextStep = mBrowseTree.getNextStepToFolder(target);
- if (DBG) {
- Log.d(TAG, "NAVIGATING From " + mBrowseTree.getCurrentBrowsedFolder().toString());
- Log.d(TAG, "NAVIGATING Toward " + target.toString());
- }
+ logD("NAVIGATING From "
+ + mBrowseTree.getCurrentBrowsedFolder().toString());
+ logD("NAVIGATING Toward " + target.toString());
if (mNextStep == null) {
- sendMessage(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ return;
} else if (target.equals(mBrowseTree.mNowPlayingNode)
- || target.equals(mBrowseTree.mRootNode)
- || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
+ || target.equals(mBrowseTree.mRootNode)
+ || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
fetchContents(mNextStep);
} else if (mNextStep.isPlayer()) {
- if (DBG) Log.d(TAG, "NAVIGATING Player " + mNextStep.toString());
+ logD("NAVIGATING Player " + mNextStep.toString());
if (mNextStep.isBrowsable()) {
AvrcpControllerService.setBrowsedPlayerNative(
- mRemoteDevice.getBluetoothAddress(), mNextStep.getPlayerID());
+ mDeviceAddress, (int) mNextStep.getBluetoothID());
} else {
- if (DBG) Log.d(TAG, "Player doesn't support browsing");
+ logD("Player doesn't support browsing");
mNextStep.setCached(true);
- broadcastFolderList(mNextStep.getID());
transitionTo(mConnected);
}
} else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
- if (DBG) Log.d(TAG, "NAVIGATING UP " + mNextStep.toString());
+ logD("NAVIGATING UP " + mNextStep.toString());
mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
mBrowseTree.getCurrentBrowsedFolder().setCached(false);
AvrcpControllerService.changeFolderPathNative(
- mRemoteDevice.getBluetoothAddress(),
+ mDeviceAddress,
AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
- AvrcpControllerService.hexStringToByteUID(null));
+ 0);
} else {
- if (DBG) Log.d(TAG, "NAVIGATING DOWN " + mNextStep.toString());
+ logD("NAVIGATING DOWN " + mNextStep.toString());
AvrcpControllerService.changeFolderPathNative(
- mRemoteDevice.getBluetoothAddress(),
+ mDeviceAddress,
AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
- AvrcpControllerService.hexStringToByteUID(mNextStep.getFolderUID()));
+ mNextStep.getBluetoothID());
}
}
@Override
public void exit() {
- mBrowseNode = null;
- super.exit();
- }
- }
-
- class SetAddresedPlayerAndPlayItem extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
- int mScope;
- String mPlayItemId;
- String mAddrPlayerId;
-
- public void setItemAndScope(String addrPlayerId, String playItemId, int scope) {
- mAddrPlayerId = addrPlayerId;
- mPlayItemId = playItemId;
- mScope = scope;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
- switch (msg.what) {
- case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
- // Set the new addressed player.
- mBrowseTree.setCurrentAddressedPlayer(mAddrPlayerId);
-
- // And now play the item.
- AvrcpControllerService.playItemNative(mRemoteDevice.getBluetoothAddress(),
- (byte) mScope, AvrcpControllerService.hexStringToByteUID(mPlayItemId),
- (int) 0);
-
- // Transition to connected state here.
- transitionTo(mConnected);
- break;
-
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- transitionTo(mConnected);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " 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);
+ mBrowseNode = null;
+ super.exit();
}
}
- // Interface APIs
- boolean isConnected() {
- synchronized (mLock) {
- return mIsConnected;
+ protected class Disconnecting extends State {
+ @Override
+ public void enter() {
+ onBrowsingDisconnected();
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+ transitionTo(mDisconnected);
}
}
- 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) {
- if (mRemoteDevice == null) return;
- BluetoothDevice device = mRemoteDevice.mBTDevice;
- if (device == null) return;
- ProfileService.println(sb, "mCurrentDevice: " + device.getAddress() + "("
- + device.getName() + ") " + 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 EMPTY_MEDIA_METADATA;
- }
- }
-
- PlaybackState getCurrentPlayBackState() {
- return getCurrentPlayBackState(true);
- }
-
- PlaybackState getCurrentPlayBackState(boolean cached) {
- if (cached) {
- synchronized (mLock) {
- if (mAddressedPlayer == null) {
- return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build();
- }
- return mAddressedPlayer.getPlaybackState();
- }
- } else {
- // Issue a native request, we return NULL since this is only for PTS.
- AvrcpControllerService.getPlaybackStateNative(mRemoteDevice.getBluetoothAddress());
- return null;
- }
- }
-
- List<MediaItem> getContents(String uid) {
- BrowseTree.BrowseNode currentNode = mBrowseTree.findBrowseNodeByID(uid);
-
- if (DBG) Log.d(TAG, "getContents(" + uid + ") currentNode = " + currentNode);
- if (currentNode != null) {
- if (!currentNode.isCached()) {
- sendMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, uid);
- }
- return currentNode.getContents();
- }
- return null;
- }
-
- public void fetchAttrAndPlayItem(String uid) {
- BrowseTree.BrowseNode currItem = mBrowseTree.findBrowseNodeByID(uid);
- BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
- if (DBG) Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
- if (currItem != null) {
- int scope = currItem.getParent().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 (VDBG) {
- Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
- }
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
- }
-
- private void broadcastFolderList(String id) {
- Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
- if (VDBG) Log.d(TAG, "broadcastFolderList id " + id);
- intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
- BluetoothMediaBrowserService bluetoothMediaBrowserService =
- BluetoothMediaBrowserService.getBluetoothMediaBrowserService();
- if (bluetoothMediaBrowserService != null) {
- bluetoothMediaBrowserService.processInternalEvent(intent);
- }
- }
-
- 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;
- if (DBG) {
- 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;
- if (DBG) Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
+ int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
+ logD(" 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);
}
- AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
- label);
+ AvrcpControllerService.sendAbsVolRspNative(mDeviceAddress, 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() {
+ MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
@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 void onPlay() {
+ logD("onPlay");
+ onPrepare();
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
+ }
+
+ @Override
+ public void onPause() {
+ logD("onPause");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ logD("onSkipToNext");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD);
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ logD("onSkipToPrevious");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD);
+ }
+
+ @Override
+ public void onSkipToQueueItem(long id) {
+ logD("onSkipToQueueItem" + id);
+ BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
+ if (node != null) {
+ sendMessage(MESSAGE_PLAY_ITEM, node);
}
}
+
+ @Override
+ public void onStop() {
+ logD("onStop");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
+ }
+
+ @Override
+ public void onPrepare() {
+ logD("onPrepare");
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if (a2dpSinkService != null) {
+ a2dpSinkService.requestAudioFocus(mDevice, true);
+ }
+ }
+
+ @Override
+ public void onRewind() {
+ logD("onRewind");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND);
+ }
+
+ @Override
+ public void onFastForward() {
+ logD("onFastForward");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+ }
+
+ @Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ // Play the item if possible.
+ onPrepare();
+ BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
+ Log.w(TAG, "Play Node not found");
+ sendMessage(MESSAGE_PLAY_ITEM, node);
+ }
};
- 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;
+ protected void broadcastConnectionStateChanged(int currentState) {
+ if (mMostRecentState == currentState) {
+ return;
}
- return str;
+ if (currentState == BluetoothProfile.STATE_CONNECTED) {
+ MetricsLogger.logProfileConnectionEvent(
+ BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
+ }
+ logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState);
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mMostRecentState = currentState;
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private boolean shouldRequestFocus() {
+ return mService.getResources()
+ .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
}
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index 8e1af83..bed38d9 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,7 +16,9 @@
package com.android.bluetooth.avrcpcontroller;
+import android.media.MediaMetadata;
import android.media.session.PlaybackState;
+import android.os.SystemClock;
import android.util.Log;
import java.util.Arrays;
@@ -26,8 +28,7 @@
*/
class AvrcpPlayer {
private static final String TAG = "AvrcpPlayer";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
public static final int INVALID_ID = -1;
@@ -42,19 +43,25 @@
private int mPlayStatus = PlaybackState.STATE_NONE;
private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ private long mPlayTimeUpdate = 0;
+ private float mPlaySpeed = 1;
private int mId;
private String mName = "";
private int mPlayerType;
private byte[] mPlayerFeatures;
private long mAvailableActions;
- private TrackInfo mCurrentTrack = new TrackInfo();
+ private MediaMetadata mCurrentTrack;
+ private PlaybackState mPlaybackState;
AvrcpPlayer() {
mId = INVALID_ID;
//Set Default Actions in case Player data isn't available.
mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
- | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
- | PlaybackState.ACTION_STOP;
+ | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackState.ACTION_STOP;
+ PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ .setActions(mAvailableActions);
+ mPlaybackState = playbackStateBuilder.build();
}
AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
@@ -64,6 +71,9 @@
mPlayerType = playerType;
mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
updateAvailableActions();
+ PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ .setActions(mAvailableActions);
+ mPlaybackState = playbackStateBuilder.build();
}
public int getId() {
@@ -76,6 +86,9 @@
public void setPlayTime(int playTime) {
mPlayTime = playTime;
+ mPlayTimeUpdate = SystemClock.elapsedRealtime();
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaySpeed).build();
}
public long getPlayTime() {
@@ -83,7 +96,29 @@
}
public void setPlayStatus(int playStatus) {
+ mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
+ - mPlaybackState.getLastPositionUpdateTime());
mPlayStatus = playStatus;
+ switch (mPlayStatus) {
+ case PlaybackState.STATE_STOPPED:
+ mPlaySpeed = 0;
+ break;
+ case PlaybackState.STATE_PLAYING:
+ mPlaySpeed = 1;
+ break;
+ case PlaybackState.STATE_PAUSED:
+ mPlaySpeed = 0;
+ break;
+ case PlaybackState.STATE_FAST_FORWARDING:
+ mPlaySpeed = 3;
+ break;
+ case PlaybackState.STATE_REWINDING:
+ mPlaySpeed = -3;
+ break;
+ }
+
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaySpeed).build();
}
public int getPlayStatus() {
@@ -100,41 +135,19 @@
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)
- .setActions(mAvailableActions).setActiveQueueItemId(mCurrentTrack.mTrackNum - 1)
- .build();
+ return mPlaybackState;
}
- public synchronized boolean updateCurrentTrack(TrackInfo update) {
- if (update != null && mCurrentTrack != null
- && update.toString().equals(mCurrentTrack.toString())) {
- if (DBG) Log.d(TAG, "Update same as original");
- return false;
+ public synchronized void updateCurrentTrack(MediaMetadata update) {
+ if (update != null) {
+ long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setActiveQueueItemId(
+ trackNumber - 1).build();
}
- if (VDBG) Log.d(TAG, "Track Changed Was:" + mCurrentTrack + "now " + update);
mCurrentTrack = update;
- return true;
}
- public synchronized TrackInfo getCurrentTrack() {
+ public synchronized MediaMetadata getCurrentTrack() {
return mCurrentTrack;
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 4dfb8d4..befe141 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -16,39 +16,18 @@
package com.android.bluetooth.avrcpcontroller;
-import android.bluetooth.BluetoothAvrcpController;
-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.AudioAttributes;
-import android.media.AudioManager;
import android.media.MediaMetadata;
-import android.media.MediaPlayer;
-import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.service.media.MediaBrowserService;
import android.util.Log;
-import android.util.Pair;
import com.android.bluetooth.R;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Implements the MediaBrowserService interface to AVRCP and A2DP
@@ -66,109 +45,15 @@
*/
public class BluetoothMediaBrowserService extends MediaBrowserService {
private static final String TAG = "BluetoothMediaBrowserService";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
-
- 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 sent when A2DP device is connected.
- private static final int MSG_DEVICE_CONNECT = 2;
- // 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;
- // Internal message to trigger a getplaystatus command to remote.
- private static final int MSG_AVRCP_GET_PLAY_STATUS_NATIVE = 6;
- // Message sent when AVRCP browse is connected.
- private static final int MSG_DEVICE_BROWSE_CONNECT = 7;
- // Message sent when AVRCP browse is disconnected.
- private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
- // Message sent when folder list is fetched.
- private static final int MSG_FOLDER_LIST = 9;
-
- // Custom actions for PTS testing.
- private static final String CUSTOM_ACTION_VOL_UP =
- "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_UP";
- private static final String CUSTOM_ACTION_VOL_DN =
- "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_DN";
- private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
- "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
- // In order to be considered as an audio source capable of receiving media key events (In the
- // eyes of MediaSessionService), we need an active MediaPlayer in addition to a MediaSession.
- // Because of this, the media player below plays an incredibly short, silent audio sample so
- // that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the current
- // active player and send us media events. This is a restriction currently imposed by the media
- // framework code and could be reconsidered in the future.
private MediaSession mSession;
- private MediaMetadata mA2dpMetadata;
- private MediaPlayer mMediaPlayer;
-
- private AvrcpControllerService mAvrcpCtrlSrvc;
- private boolean mBrowseConnected = false;
- private BluetoothDevice mA2dpDevice = null;
- private A2dpSinkService mA2dpSinkService = null;
- private Handler mAvrcpCommandQueue;
- private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
- private int mCurrentlyHeldKey = 0;
// Browsing related structures.
private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
- private static final class AvrcpCommandQueueHandler extends Handler {
- WeakReference<BluetoothMediaBrowserService> mInst;
-
- AvrcpCommandQueueHandler(Looper looper, BluetoothMediaBrowserService sink) {
- super(looper);
- mInst = new WeakReference<BluetoothMediaBrowserService>(sink);
- }
-
- @Override
- public void handleMessage(Message msg) {
- BluetoothMediaBrowserService inst = mInst.get();
- if (inst == null) {
- Log.e(TAG, "Parent class has died; aborting.");
- return;
- }
-
- switch (msg.what) {
- case MSG_DEVICE_CONNECT:
- inst.msgDeviceConnect((BluetoothDevice) msg.obj);
- break;
- case MSG_DEVICE_DISCONNECT:
- inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
- break;
- case MSG_TRACK:
- Pair<PlaybackState, MediaMetadata> pair =
- (Pair<PlaybackState, MediaMetadata>) (msg.obj);
- inst.msgTrack(pair.first, pair.second);
- break;
- case MSG_AVRCP_PASSTHRU:
- inst.msgPassThru((int) msg.obj);
- break;
- case MSG_AVRCP_GET_PLAY_STATUS_NATIVE:
- inst.msgGetPlayStatusNative();
- 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);
- }
- }
- }
-
/**
* Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
* MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
@@ -180,133 +65,34 @@
// Create and configure the MediaSession
mSession = new MediaSession(this, TAG);
- mSession.setCallback(mSessionCallbacks);
+ setSessionToken(mSession.getSessionToken());
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
-
- // Create and setup the MediaPlayer
- initMediaPlayer();
-
- // Associate the held MediaSession with this browser and activate it
- setSessionToken(mSession.getSessionToken());
- mSession.setActive(true);
-
- // Internal handler to process events and requests
- mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
-
- // Set the initial Media state (sets current playback state and media meta data)
- refreshInitialPlayingState();
-
- // Set up communication with the controller service
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- 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();
- }
-
- setBluetoothMediaBrowserService(this);
+ sBluetoothMediaBrowserService = this;
}
- /**
- * Clean up this instance in the reverse order that we created it.
- */
+ List<MediaItem> getContents(final String parentMediaId) {
+ AvrcpControllerService avrcpControllerService =
+ AvrcpControllerService.getAvrcpControllerService();
+ if (avrcpControllerService == null) {
+ return new ArrayList(0);
+ } else {
+ return avrcpControllerService.getContents(parentMediaId);
+ }
+ }
+
@Override
- public void onDestroy() {
- if (DBG) Log.d(TAG, "onDestroy");
- setBluetoothMediaBrowserService(null);
- unregisterReceiver(mBtReceiver);
- destroyMediaPlayer();
- mSession.release();
- super.onDestroy();
- }
-
- /**
- * Initializes the silent MediaPlayer object which aids in receiving media key focus.
- *
- * The created MediaPlayer is already prepared and will release and stop itself on error. All
- * you need to do is start() it.
- */
- private void initMediaPlayer() {
- if (DBG) Log.d(TAG, "initMediaPlayer()");
-
- // Parameters for create
- AudioAttributes attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
- AudioManager am = getSystemService(AudioManager.class);
-
- // Create our player object. Returns a prepared player on success, null on failure
- mMediaPlayer = MediaPlayer.create(this, R.raw.silent, attrs, am.generateAudioSessionId());
- if (mMediaPlayer == null) {
- Log.e(TAG, "Failed to initialize media player. You may not get media key events");
- return;
+ public synchronized void onLoadChildren(final String parentMediaId,
+ final Result<List<MediaItem>> result) {
+ if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
+ List<MediaItem> contents = getContents(parentMediaId);
+ if (contents == null) {
+ result.detach();
+ } else {
+ result.sendResult(contents);
}
-
- // Set other player attributes
- mMediaPlayer.setLooping(false);
- mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
- Log.e(TAG, "Silent media player error: " + what + ", " + extra);
- destroyMediaPlayer();
- return false;
- });
- }
-
- /**
- * Safely tears down our local MediaPlayer
- */
- private void destroyMediaPlayer() {
- if (DBG) Log.d(TAG, "destroyMediaPlayer()");
- if (mMediaPlayer == null) {
- return;
- }
- mMediaPlayer.stop();
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
-
- /**
- * Uses the internal MediaPlayer to play a silent, short audio sample so that AudioService will
- * treat us as the active MediaSession/MediaPlayer combo and properly route us media key events.
- *
- * If the MediaPlayer failed to initialize properly, this call will fail gracefully and log the
- * failed attempt. Media keys will not be routed.
- */
- private void getMediaKeyFocus() {
- if (DBG) Log.d(TAG, "getMediaKeyFocus()");
- if (mMediaPlayer == null) {
- Log.w(TAG, "Media player is null. Can't get media key focus. Media keys may not route");
- return;
- }
- mMediaPlayer.start();
- }
-
- /**
- * getBluetoothMediaBrowserService()
- * Routine to get direct access to MediaBrowserService from within the same process.
- */
- public static synchronized BluetoothMediaBrowserService getBluetoothMediaBrowserService() {
- if (sBluetoothMediaBrowserService == null) {
- Log.w(TAG, "getBluetoothMediaBrowserService(): service is NULL");
- return null;
- }
- if (DBG) {
- Log.d(TAG, "getBluetoothMediaBrowserService(): returning "
- + sBluetoothMediaBrowserService);
- }
- return sBluetoothMediaBrowserService;
- }
-
- private static synchronized void setBluetoothMediaBrowserService(
- BluetoothMediaBrowserService instance) {
- if (DBG) Log.d(TAG, "setBluetoothMediaBrowserService(): set to: " + instance);
- sBluetoothMediaBrowserService = instance;
}
@Override
@@ -315,411 +101,83 @@
return new BrowserRoot(BrowseTree.ROOT, null);
}
- @Override
- public synchronized void onLoadChildren(final String parentMediaId,
- final Result<List<MediaItem>> result) {
- if (mAvrcpCtrlSrvc == null) {
- Log.w(TAG, "AVRCP not yet connected.");
- result.sendResult(Collections.emptyList());
- return;
- }
-
- if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
- List<MediaItem> contents = mAvrcpCtrlSrvc.getContents(mA2dpDevice, parentMediaId);
- if (contents == null) {
- mParentIdToRequestMap.put(parentMediaId, result);
- result.detach();
- } else {
- result.sendResult(contents);
- }
-
- return;
- }
-
- @Override
- public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
- }
-
- // Media Session Stuff.
- private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
- @Override
- public void onPlay() {
- if (DBG) Log.d(TAG, "onPlay");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onPause() {
- if (DBG) Log.d(TAG, "onPause");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onSkipToNext() {
- if (DBG) Log.d(TAG, "onSkipToNext");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onSkipToPrevious() {
- if (DBG) Log.d(TAG, "onSkipToPrevious");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onSkipToQueueItem(long id) {
- if (DBG) Log.d(TAG, "onSkipToQueueItem" + id);
- if (mA2dpSinkService != null) {
- mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
- }
- MediaSession.QueueItem queueItem = mMediaQueue.get((int) id);
- if (queueItem != null) {
- String mediaId = queueItem.getDescription().getMediaId();
- mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
- }
- }
-
- @Override
- public void onStop() {
- if (DBG) Log.d(TAG, "onStop");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_STOP).sendToTarget();
- }
-
- @Override
- public void onPrepare() {
- if (DBG) Log.d(TAG, "onPrepare");
- if (mA2dpSinkService != null) {
- mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
- getMediaKeyFocus();
- }
- }
-
- @Override
- public void onRewind() {
- if (DBG) Log.d(TAG, "onRewind");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onFastForward() {
- if (DBG) Log.d(TAG, "onFastForward");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onPlayFromMediaId(String mediaId, Bundle extras) {
- synchronized (BluetoothMediaBrowserService.this) {
- // Play the item if possible.
- if (mA2dpSinkService != null) {
- mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
- getMediaKeyFocus();
- }
- mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
- }
-
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- // Support VOL UP and VOL DOWN events for PTS testing.
- @Override
- public void onCustomAction(String action, Bundle extras) {
- if (DBG) Log.d(TAG, "onCustomAction " + action);
- if (CUSTOM_ACTION_VOL_UP.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_VOL_UP).sendToTarget();
- } else if (CUSTOM_ACTION_VOL_DN.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_VOL_DOWN).sendToTarget();
- } else if (CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
- } else {
- Log.w(TAG, "Custom action " + action + " not supported.");
- }
- }
- };
-
- private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DBG) Log.d(TAG, "onReceive intent=" + intent);
- String action = intent.getAction();
- BluetoothDevice btDev =
- (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-
- if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- if (DBG) {
- Log.d(TAG, "handleConnectionStateChange: newState="
- + state + " btDev=" + btDev);
- }
-
- // Connected state will be handled when AVRCP BluetoothProfile gets connected.
- if (state == BluetoothProfile.STATE_CONNECTED) {
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
- } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // Set the playback state to unconnected.
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
- // If we have been pushing updates via the session then stop sending them since
- // we are not connected anymore.
- if (mSession.isActive()) {
- mSession.setActive(false);
- }
- }
- } 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(AvrcpControllerService.EXTRA_PLAYBACK);
- MediaMetadata mmd =
- 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 synchronized void msgDeviceConnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceConnect");
- // We are connected to a new device via A2DP now.
- mA2dpDevice = device;
- 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 synchronized void refreshInitialPlayingState() {
- if (mA2dpDevice == null) {
- if (DBG) Log.d(TAG, "device " + mA2dpDevice);
- return;
- }
-
- List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
- if (devices.size() == 0) {
- Log.w(TAG, "No devices connected yet");
- return;
- }
-
- if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
- Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
- return;
- }
- mA2dpDevice = devices.get(0);
- mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
-
- PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
- MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
- if (VDBG) {
- Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
- }
- mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
- mSession.setPlaybackState(playbackState);
- }
-
- private void msgDeviceDisconnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceDisconnect");
- if (mA2dpDevice == null) {
- Log.w(TAG, "Already disconnected - nothing to do here.");
- return;
- } else if (!mA2dpDevice.equals(device)) {
- Log.e(TAG,
- "Not the right device to disconnect current " + mA2dpDevice + " dc " + device);
- return;
- }
-
- // Unset the session.
- PlaybackState.Builder pbb = new PlaybackState.Builder();
- pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- PLAYBACK_SPEED)
- .setErrorMessage(getString(R.string.bluetooth_disconnected));
- mSession.setPlaybackState(pbb.build());
-
- // Set device to null.
- mA2dpDevice = null;
- mBrowseConnected = false;
- // update playerList.
+ private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
+ List<MediaItem> songList = node.getContents();
mMediaQueue.clear();
- mSession.setQueue(mMediaQueue);
- notifyChildrenChanged("__ROOT__");
- }
-
- private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
- if (VDBG) Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
- // Log the current track position/content.
- MediaController controller = mSession.getController();
- PlaybackState prevPS = controller.getPlaybackState();
- MediaMetadata prevMM = controller.getMetadata();
-
- if (prevPS != null) {
- Log.d(TAG, "prevPS " + prevPS);
- }
-
- if (prevMM != null) {
- String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
- long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
- }
-
- if (mmd != null) {
- if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
- mSession.setMetadata(mmd);
- }
-
- if (pb != null) {
- if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
- mSession.setPlaybackState(pb);
-
- // If we are now playing then we should start pushing updates via MediaSession so that
- // external UI (such as SystemUI) can show the currently playing music.
- if (pb.getState() == PlaybackState.STATE_PLAYING && !mSession.isActive()) {
- mSession.setActive(true);
+ if (songList != null) {
+ for (MediaItem song : songList) {
+ mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(),
+ mMediaQueue.size()));
}
}
- }
-
- private boolean isHoldableKey(int cmd) {
- return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
- || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
- }
-
- private synchronized void msgPassThru(int cmd) {
- if (DBG) Log.d(TAG, "msgPassThru " + cmd);
- if (mA2dpDevice == null) {
- // We should have already disconnected - ignore this message.
- Log.w(TAG, "Already disconnected ignoring.");
- return;
- }
- // Some keys should be held until the next event.
- if (mCurrentlyHeldKey != 0) {
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, mCurrentlyHeldKey,
- AvrcpControllerService.KEY_STATE_RELEASED);
-
- if (mCurrentlyHeldKey == cmd) {
- // Return to prevent starting FF/FR operation again
- mCurrentlyHeldKey = 0;
- return;
- } else {
- // FF/FR is in progress and other operation is desired
- // so after stopping FF/FR, not returning so that command
- // can be sent for the desired operation.
- mCurrentlyHeldKey = 0;
- }
- }
-
- // Send the pass through.
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
- AvrcpControllerService.KEY_STATE_PRESSED);
-
- if (isHoldableKey(cmd)) {
- // Release cmd next time a command is sent.
- mCurrentlyHeldKey = cmd;
- } else {
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
- }
-
- private synchronized void msgGetPlayStatusNative() {
- if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
- if (mA2dpDevice == null) {
- // We should have already disconnected - ignore this message.
- Log.w(TAG, "Already disconnected ignoring.");
- return;
- }
-
- // Ask for a non cached version.
- mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice, false);
- }
-
- private void msgDeviceBrowseConnect(BluetoothDevice device) {
- if (DBG) 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;
- // update playerList
- notifyChildrenChanged("__ROOT__");
- }
-
- private void msgFolderList(Intent intent) {
- // Parse the folder list for children list and id.
- String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
- updateNowPlayingQueue();
- if (VDBG) Log.d(TAG, "Parent: " + id);
- synchronized (this) {
- // If we have a result object then we should send the result back
- // to client since it is blocking otherwise we may have gotten more items
- // from remote device, hence let client know to fetch again.
- Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
- if (results == null) {
- Log.w(TAG, "Request no longer exists, notifying that children changed.");
- notifyChildrenChanged(id);
- } else {
- List<MediaItem> folderList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, id);
- results.sendResult(folderList);
- }
- }
- }
-
- private void updateNowPlayingQueue() {
- List<MediaItem> songList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, "NOW_PLAYING");
- Log.d(TAG, "NowPlaying" + songList.size());
- mMediaQueue.clear();
- for (MediaItem song : songList) {
- mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(), mMediaQueue.size()));
- }
mSession.setQueue(mMediaQueue);
}
+ static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
+ if (sBluetoothMediaBrowserService != null) {
+ if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+ sBluetoothMediaBrowserService.updateNowPlayingQueue(node);
+ } else {
+ sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID());
+ }
+ }
+ }
+
+ static synchronized void addressedPlayerChanged(MediaSession.Callback callback) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setCallback(callback);
+ } else {
+ Log.w(TAG, "addressedPlayerChanged Unavailable");
+ }
+ }
+
+ static synchronized void trackChanged(MediaMetadata mediaMetadata) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata);
+ } else {
+ Log.w(TAG, "trackChanged Unavailable");
+ }
+ }
+
+ static synchronized void notifyChanged(PlaybackState playbackState) {
+ Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
+ } else {
+ Log.w(TAG, "notifyChanged Unavailable");
+ }
+ }
+
/**
- * processInternalEvent(Intent intent)
- * Routine to provide MediaBrowserService with content updates from within the same process.
+ * Send AVRCP Play command
*/
- public void processInternalEvent(Intent intent) {
- String action = intent.getAction();
- if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
+ public static synchronized void play() {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.getController().getTransportControls().play();
+ } else {
+ Log.w(TAG, "play Unavailable");
}
}
- private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
- if (DBG) 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;
+ /**
+ * Send AVRCP Pause command
+ */
+ public static synchronized void pause() {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.getController().getTransportControls().pause();
+ } else {
+ Log.w(TAG, "pause Unavailable");
}
- mBrowseConnected = false;
}
+ /**
+ * Set Media session active whenever we have Focus of any kind
+ */
+ public static synchronized void setActive(boolean active) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setActive(active);
+ } else {
+ Log.w(TAG, "setActive Unavailable");
+ }
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 7f4f3bf..542beee 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -16,6 +16,7 @@
package com.android.bluetooth.avrcpcontroller;
+import android.bluetooth.BluetoothDevice;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
@@ -25,6 +26,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
// Browsing hierarchy.
// Root:
@@ -39,8 +41,8 @@
// ....
public class BrowseTree {
private static final String TAG = "BrowseTree";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
public static final String ROOT = "__ROOT__";
public static final String UP = "__UP__";
@@ -57,25 +59,28 @@
final BrowseNode mNavigateUpNode;
final BrowseNode mNowPlayingNode;
- BrowseTree() {
- Bundle mdBundle = new Bundle();
- mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
- mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder().setExtras(mdBundle)
- .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+ BrowseTree(BluetoothDevice device) {
+ if (device == null) {
+ mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+ mRootNode.setCached(true);
+ } else {
+ mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(ROOT + device.getAddress().toString()).setTitle(
+ device.getName()).build(), MediaItem.FLAG_BROWSABLE));
+ mRootNode.mDevice = device;
+
+ }
mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
mRootNode.setExpectedChildren(255);
- Bundle upnodeBundle = new Bundle();
- upnodeBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, UP);
mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
- .setExtras(upnodeBundle).setMediaId(UP).setTitle(UP).build(),
- MediaItem.FLAG_BROWSABLE));
+ .setMediaId(UP).setTitle(UP).build(),
+ MediaItem.FLAG_BROWSABLE));
- Bundle nowPlayingBundle = new Bundle();
- nowPlayingBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, NOW_PLAYING_PREFIX);
mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
- .setExtras(nowPlayingBundle).setMediaId(NOW_PLAYING_PREFIX)
- .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
+ .setMediaId(NOW_PLAYING_PREFIX)
+ .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
mNowPlayingNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING;
mNowPlayingNode.setExpectedChildren(255);
mBrowseMap.put(ROOT, mRootNode);
@@ -89,11 +94,23 @@
mBrowseMap.clear();
}
+ void onConnected(BluetoothDevice device) {
+ BrowseNode browseNode = new BrowseNode(device);
+ mRootNode.addChild(browseNode);
+ }
+
+ BrowseNode getTrackFromNowPlayingList(int trackNumber) {
+ return mNowPlayingNode.mChildren.get(trackNumber);
+ }
+
// 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;
+ BluetoothDevice mDevice;
+ long mBluetoothId;
+
// Type of this browse node.
// Since Media APIs do not define the player separately we define that
// distinction here.
@@ -103,7 +120,7 @@
// without doing another fetch.
boolean mCached = false;
- int mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
+ byte mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
// List of children.
private BrowseNode mParent;
@@ -112,6 +129,10 @@
BrowseNode(MediaItem item) {
mItem = item;
+ Bundle extras = mItem.getDescription().getExtras();
+ if (extras != null) {
+ mBluetoothId = extras.getLong(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+ }
}
BrowseNode(AvrcpPlayer player) {
@@ -119,22 +140,29 @@
// 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);
+ mBluetoothId = player.getId();
+
+ mdb.setMediaId(UUID.randomUUID().toString());
mdb.setTitle(player.getName());
int mediaItemFlags = player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING)
? MediaBrowser.MediaItem.FLAG_BROWSABLE : 0;
mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
}
+ BrowseNode(BluetoothDevice device) {
+ boolean mIsPlayer = true;
+ mDevice = device;
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ String playerKey = PLAYER_PREFIX + device.getAddress().toString();
+ mdb.setMediaId(playerKey);
+ mdb.setTitle(device.getName());
+ int mediaItemFlags = MediaBrowser.MediaItem.FLAG_BROWSABLE;
+ mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+ }
+
private BrowseNode(String name) {
MediaDescription.Builder mdb = new MediaDescription.Builder();
- Bundle mdExtra = new Bundle();
- mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, name);
- mdb.setExtras(mdExtra);
mdb.setMediaId(name);
mdb.setTitle(name);
mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
@@ -156,15 +184,32 @@
} else if (child instanceof AvrcpPlayer) {
currentNode = new BrowseNode((AvrcpPlayer) child);
}
- if (currentNode != null) {
- currentNode.mParent = this;
- mChildren.add(currentNode);
- mBrowseMap.put(currentNode.getID(), currentNode);
- }
+ addChild(currentNode);
}
return newChildren.size();
}
+ synchronized boolean addChild(BrowseNode node) {
+ if (node != null) {
+ node.mParent = this;
+ if (this.mBrowseScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+ node.mBrowseScope = this.mBrowseScope;
+ }
+ if (node.mDevice == null) {
+ node.mDevice = this.mDevice;
+ }
+ mChildren.add(node);
+ mBrowseMap.put(node.getID(), node);
+ return true;
+ }
+ return false;
+ }
+
+ synchronized void removeChild(BrowseNode node) {
+ mChildren.remove(node);
+ mBrowseMap.remove(node.getID());
+ }
+
synchronized int getChildrenCount() {
return mChildren.size();
}
@@ -178,7 +223,7 @@
}
synchronized List<MediaItem> getContents() {
- if (mChildren != null) {
+ if (mChildren.size() > 0 || mCached) {
List<MediaItem> contents = new ArrayList<MediaItem>(mChildren.size());
for (BrowseNode child : mChildren) {
contents.add(child.getMediaItem());
@@ -221,7 +266,7 @@
return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
}
- synchronized int getScope() {
+ synchronized byte getScope() {
return mBrowseScope;
}
@@ -229,9 +274,11 @@
// 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);
+ return getID();
+ }
+
+ synchronized long getBluetoothID() {
+ return mBluetoothId;
}
synchronized MediaItem getMediaItem() {
@@ -256,14 +303,20 @@
}
@Override
- public String toString() {
+ public synchronized String toString() {
if (VDBG) {
- return "[ Name: " + mItem.getDescription().getTitle() + " expected Children: "
+ String serialized = "[ Name: " + mItem.getDescription().getTitle()
+ + " Scope:" + mBrowseScope + " expected Children: "
+ mExpectedChildrenCount + "] ";
+ for (BrowseNode node : mChildren) {
+ serialized += node.toString();
+ }
+ return serialized;
} else {
return "ID: " + getID();
}
}
+
// Returns true if target is a descendant of this.
synchronized boolean isDescendant(BrowseNode target) {
return getEldestChild(this, target) == null ? false : true;
@@ -312,6 +365,7 @@
for (Integer level = 0; level < depth; level++) {
BrowseNode dummyNode = new BrowseNode(level.toString());
dummyNode.mParent = mCurrentBrowseNode;
+ dummyNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
mCurrentBrowseNode = dummyNode;
}
mCurrentBrowseNode.setExpectedChildren(items);
@@ -342,7 +396,11 @@
@Override
public String toString() {
- return "Size: " + mBrowseMap.size();
+ String serialized = "Size: " + mBrowseMap.size();
+ if (VDBG) {
+ serialized += mRootNode.toString();
+ }
+ return serialized;
}
// Calculates the path to target node.
@@ -373,21 +431,14 @@
return nextChild;
}
}
- /*
- if (mCurrentBrowseNode.isDescendant(target)) {
- return getEldestChild(mCurrentBrowseNode, target);
- } else {
- if (DBG) Log.d(TAG, "NAVIGATING UP");
- return mNavigateUpNode;
- }
- */
}
- BrowseNode getEldestChild(BrowseNode ancestor, BrowseNode target) {
+ static BrowseNode getEldestChild(BrowseNode ancestor, BrowseNode target) {
// ancestor is an ancestor of target
BrowseNode descendant = target;
if (DBG) {
- Log.d(TAG, "NAVIGATING ancestor" + ancestor.toString() + "Target" + target.toString());
+ Log.d(TAG, "NAVIGATING ancestor" + ancestor.toString() + "Target"
+ + target.toString());
}
while (!ancestor.equals(descendant.mParent)) {
descendant = descendant.mParent;
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
deleted file mode 100644
index 7946747..0000000
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.BluetoothDevice;
-
-import com.android.bluetooth.Utils;
-
-/*
- * 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;
- }
-
- public synchronized byte[] getBluetoothAddress() {
- return Utils.getByteAddress(mBTDevice);
- }
-
- public synchronized void setNotificationLabel(int label) {
- mNotificationLabel = label;
- }
-
- public synchronized int getNotificationLabel() {
- return mNotificationLabel;
- }
-
- public synchronized void setAbsVolNotificationRequested(boolean request) {
- mAbsVolNotificationRequested = request;
- }
-
- public synchronized boolean getAbsVolNotificationRequested() {
- return mAbsVolNotificationRequested;
- }
-
- public synchronized void setFirstAbsVolCmdRecvd() {
- mFirstAbsVolCmdRecvd = true;
- }
-
- public synchronized boolean getFirstAbsVolCmdRecvd() {
- return mFirstAbsVolCmdRecvd;
- }
-}
diff --git a/src/com/android/bluetooth/avrcpcontroller/StackEvent.java b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
new file mode 100644
index 0000000..1f96bd2
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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;
+
+final class StackEvent {
+ // Event types for STACK_EVENT message
+ static final int EVENT_TYPE_NONE = 0;
+ static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ static final int EVENT_TYPE_RC_FEATURES = 2;
+
+ int mType = EVENT_TYPE_NONE;
+ boolean mRemoteControlConnected;
+ boolean mBrowsingConnected;
+ int mFeatures;
+
+ private StackEvent(int type) {
+ this.mType = type;
+ }
+
+ @Override
+ public String toString() {
+ switch (mType) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mRemoteControlConnected;
+ case EVENT_TYPE_RC_FEATURES:
+ return "EVENT_TYPE_RC_FEATURES";
+ default:
+ return "Unknown";
+ }
+ }
+
+ static StackEvent connectionStateChanged(boolean remoteControlConnected,
+ boolean browsingConnected) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.mRemoteControlConnected = remoteControlConnected;
+ event.mBrowsingConnected = browsingConnected;
+ return event;
+ }
+
+ static StackEvent rcFeatures(int features) {
+ StackEvent event = new StackEvent(EVENT_TYPE_RC_FEATURES);
+ event.mFeatures = features;
+ return event;
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index 7764e60..fd1b784 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -14,34 +14,11 @@
* limitations under the License.
*/
-
package com.android.bluetooth.avrcpcontroller;
import android.media.MediaMetadata;
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/*
- * 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 VDBG = false;
-
- /*
- * 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 = "";
-
+final class TrackInfo {
/*
*Element Id Values for GetMetaData from JNI
*/
@@ -53,95 +30,49 @@
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;
- 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.
-
- TrackInfo() {
- this(new ArrayList<Integer>(), new ArrayList<String>());
- }
-
- 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));
+ static MediaMetadata getMetadata(int[] attrIds, String[] attrMap) {
+ MediaMetadata.Builder metaDataBuilder = new MediaMetadata.Builder();
+ int attributeCount = Math.max(attrIds.length, attrMap.length);
+ for (int i = 0; i < attributeCount; i++) {
+ switch (attrIds[i]) {
+ case MEDIA_ATTRIBUTE_TITLE:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_ARTIST_NAME:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_ALBUM_NAME:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Track Number doesn't parse, leave it unset
+ }
+ break;
+ case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Total Track Number doesn't parse, leave it unset
+ }
+ break;
+ case MEDIA_ATTRIBUTE_GENRE:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_PLAYING_TIME:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Playing Time doesn't parse, leave it unset
+ }
+ break;
+ }
}
-
- 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;
- }
-
- @Override
- 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 (VDBG) {
- 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();
+ return metaDataBuilder.build();
}
}
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index 1274e48..84d47c7 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -47,7 +47,7 @@
@Mock private Context mMockContext;
- @Mock private A2dpSinkStateMachine mMockA2dpSink;
+ @Mock private A2dpSinkService mMockA2dpSink;
@Mock private AudioManager mMockAudioManager;