Merge SPL-2019-07-05
Change-Id: I00781c96be237a526aadb8d7e9e1221a12cd3b1c
diff --git a/Android.mk b/Android.mk
index 5d3db9a..5ac0ecf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -12,12 +12,18 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../../vendor/qcom/opensource/bluetooth_ext/packages_apps_bluetooth_ext/src) \
+ $(call all-java-files-under, ../../../vendor/qcom/opensource/commonsys/bluetooth_ext/packages_apps_bluetooth_ext/src)
+
LOCAL_PACKAGE_NAME := Bluetooth
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_USE_AAPT2 := true
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
+
LOCAL_JAVA_LIBRARIES := javax.obex telephony-common services.net
LOCAL_STATIC_JAVA_LIBRARIES := \
com.android.vcard \
@@ -27,7 +33,11 @@
libprotobuf-java-lite \
bluetooth-protos-lite
-LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.emailcommon
+LOCAL_PROTOC_OPTIMIZE_TYPE := micro
+
LOCAL_REQUIRED_MODULES := libbluetooth
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b24064f..ebafdbc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+ <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
<uses-permission android:name="android.permission.BLUETOOTH_MAP" />
<uses-permission android:name="android.permission.DUMP" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -58,7 +59,6 @@
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
@@ -68,6 +68,8 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+ <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
<!-- For PBAP Owner Vcard Info -->
<uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -335,6 +337,14 @@
</service>
<service
android:process="@string/process"
+ android:name = ".ba.BATService"
+ android:enabled="@bool/profile_supported_ba">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothBATransmitter" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
android:name = ".hid.HidHostService"
android:enabled="@bool/profile_supported_hid_host">
<intent-filter>
diff --git a/jni/Android.bp b/jni/Android.bp
index c0fdd80..bab3d12 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -25,6 +25,7 @@
include_dirs: [
"libnativehelper/include/nativehelper",
"system/bt/types",
+ "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include",
],
shared_libs: [
"libandroid_runtime",
@@ -38,6 +39,7 @@
"libbluetooth-types",
"libutils",
"libcutils",
+ "libbluetoothqti_jni",
],
cflags: [
"-Wall",
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 9f136a0..d09d1df 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -97,7 +97,15 @@
int register_com_android_bluetooth_sdp (JNIEnv* env);
+int register_com_android_bluetooth_btservice_vendor (JNIEnv* env);
+
+int register_com_android_bluetooth_btservice_vendor_socket(JNIEnv* env);
+
int register_com_android_bluetooth_hearing_aid(JNIEnv* env);
+
+int register_com_android_bluetooth_avrcp_ext(JNIEnv* env);
+
+int register_com_android_bluetooth_ba(JNIEnv* env);
}
#endif /* COM_ANDROID_BLUETOOTH_H */
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.cpp b/jni/com_android_bluetooth_avrcp.cpp
index 1eb3553..fc72517 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -25,6 +25,8 @@
#include <inttypes.h>
#include <string.h>
+#include <mutex>
+#include <shared_mutex>
namespace android {
static jmethodID method_getRcFeatures;
@@ -46,6 +48,7 @@
static const btrc_interface_t* sBluetoothAvrcpInterface = NULL;
static jobject mCallbacksObj = NULL;
+static std::shared_timed_mutex callbacks_mutex;
/* Function declarations */
static bool copy_item_attributes(JNIEnv* env, jobject object,
@@ -61,6 +64,7 @@
static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
btrc_remote_features_t features) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -84,6 +88,7 @@
/** Callback for play status request */
static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -107,6 +112,7 @@
btrc_media_attr_t* p_attrs,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -140,6 +146,7 @@
uint32_t param,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -163,6 +170,7 @@
static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -187,6 +195,7 @@
static void btavrcp_passthrough_command_callback(int id, int pressed,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -210,6 +219,7 @@
static void btavrcp_set_addressed_player_callback(uint16_t player_id,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -233,6 +243,7 @@
static void btavrcp_set_browsed_player_callback(uint16_t player_id,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
@@ -256,6 +267,7 @@
uint8_t scope, uint32_t start_item, uint32_t end_item, uint8_t num_attr,
uint32_t* p_attr_ids, const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -296,6 +308,7 @@
static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -331,6 +344,7 @@
btrc_media_attr_t* p_attrs,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -374,6 +388,7 @@
uint8_t* uid,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
@@ -406,6 +421,7 @@
static void btavrcp_get_total_num_items_callback(uint8_t scope,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
@@ -428,6 +444,7 @@
static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len,
uint8_t* p_str, const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
@@ -460,6 +477,7 @@
uint16_t uid_counter,
const RawAddress& bd_addr) {
CallbackEnv sCallbackEnv(__func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
@@ -563,6 +581,7 @@
}
static void initNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -600,6 +619,7 @@
}
static void cleanupNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -746,18 +766,18 @@
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_FALSE;
}
- }
- for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
- pAttrs[attr_cnt].attr_id = attr[attr_cnt];
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
+ for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+ pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+ ScopedLocalRef<jstring> text(
+ env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
- if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
- env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: Failed to copy attributes", __func__);
- break;
+ if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
+ env)) {
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ ALOGE("%s: Failed to copy attributes", __func__);
+ break;
+ }
}
}
RawAddress rawAddress;
@@ -1231,24 +1251,29 @@
if (rspStatus == BTRC_STS_NO_ERROR) {
if (depth > 0) {
p_folders = new btrc_br_folder_name_t[depth];
- }
- for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
- /* copy folder names */
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
-
- if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
- text.get(), env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- delete[] p_folders;
- env->ReleaseByteArrayElements(address, addr, 0);
- ALOGE("%s: Failed to copy folder name", __func__);
+ if (!p_folders ) {
+ jniThrowIOException(env, EINVAL);
+ ALOGE("%s: not have enough memeory", __func__);
return JNI_FALSE;
}
+ for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
+ /* copy folder names */
+ ScopedLocalRef<jstring> text(
+ env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
- p_folders[folder_idx].str_len =
- strlen((char*)p_folders[folder_idx].p_str);
+ if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
+ text.get(), env)) {
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ delete[] p_folders;
+ env->ReleaseByteArrayElements(address, addr, 0);
+ ALOGE("%s: Failed to copy folder name", __func__);
+ return JNI_FALSE;
+ }
+
+ p_folders[folder_idx].str_len =
+ strlen((char*)p_folders[folder_idx].p_str);
+ }
}
}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 7710a91..cc7bb64 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -21,6 +21,7 @@
#include "android_runtime/AndroidRuntime.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_rc.h"
+#include "hardware/bt_vendor_rc.h"
#include "utils/Log.h"
#include <string.h>
@@ -35,6 +36,7 @@
static jmethodID method_handleSetAbsVolume;
static jmethodID method_handleRegisterNotificationAbsVol;
static jmethodID method_handletrackchanged;
+static jmethodID method_handleElementAttrupdate;
static jmethodID method_handleplaypositionchanged;
static jmethodID method_handleplaystatuschanged;
static jmethodID method_handleGetFolderItemsRsp;
@@ -51,6 +53,7 @@
static jclass class_AvrcpPlayer;
static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
+static const btrc_vendor_ctrl_interface_t* sBluetoothAvrcpVendorInterface = NULL;
static jobject sCallbacksObj = NULL;
static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
@@ -117,7 +120,7 @@
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
- (jint)features);
+ (jint)features, (jint) 0);
}
static void btavrcp_setplayerapplicationsetting_rsp_callback(
@@ -139,6 +142,86 @@
addr.get(), (jint)accepted);
}
+static void btavrcp_get_vendor_rcfeatures_callback(RawAddress* bd_addr, int features,
+ uint16_t cover_art_psm) {
+ ALOGV("%s", __func__);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("Fail to new jbyteArray bd addr ");
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)bd_addr);
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
+ (jint)features, (jint) cover_art_psm);
+}
+
+static void btavrcp_vendor_get_mediaelementattribute_rsp_callback(RawAddress *bd_addr,
+ uint8_t num_attr, btrc_element_attr_val_t *p_attrs) {
+ /*
+ * byteArray will be formatted like this: id,len,string
+ * Assuming text feild to be null terminated.
+ */
+ jbyteArray addr;
+ jintArray attribIds;
+ jobjectArray stringArray;
+ jstring str;
+ jclass strclazz;
+ jint i;
+ ALOGV("%s", __func__);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+
+ addr = sCallbackEnv->NewByteArray(sizeof(RawAddress));
+ if (!addr) {
+ ALOGE("Fail to get new array ");
+ return;
+ }
+ attribIds = sCallbackEnv->NewIntArray(num_attr);
+ if(!attribIds) {
+ ALOGE(" failed to set new array for attribIds");
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(RawAddress), (jbyte *) bd_addr);
+
+ strclazz = sCallbackEnv->FindClass("java/lang/String");
+ stringArray = sCallbackEnv->NewObjectArray((jint)num_attr, strclazz, 0);
+ if(!stringArray) {
+ ALOGE(" failed to get String array");
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ return;
+ }
+ for(i = 0; i < num_attr; i++)
+ {
+ str = sCallbackEnv->NewStringUTF((char*)(p_attrs[i].text));
+ if(!str) {
+ ALOGE(" Unable to get str ");
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ sCallbackEnv->DeleteLocalRef(stringArray);
+ return;
+ }
+ sCallbackEnv->SetIntArrayRegion(attribIds, i, 1, (jint*)&(p_attrs[i].attr_id));
+ sCallbackEnv->SetObjectArrayElement(stringArray, i,str);
+ sCallbackEnv->DeleteLocalRef(str);
+ }
+
+ sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleElementAttrupdate, addr,
+ (jbyte)(num_attr), attribIds, stringArray);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ /* TODO check do we need to delete str seperately or not */
+ sCallbackEnv->DeleteLocalRef(stringArray);
+ sCallbackEnv->DeleteLocalRef(strclazz);
+}
+
static void btavrcp_playerapplicationsetting_callback(
const RawAddress& bd_addr, uint8_t num_attr,
btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
@@ -367,6 +450,15 @@
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("Fail to get new array ");
+ 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 =
@@ -540,10 +632,10 @@
if (isPlayerListing) {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
- itemArray.get());
+ itemArray.get(), addr.get());
} else {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
- status, itemArray.get());
+ status, itemArray.get(), addr.get());
}
}
@@ -553,8 +645,17 @@
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("Fail to get new array ");
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
- (jint)count);
+ (jint)count, addr.get());
}
static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
@@ -564,8 +665,17 @@
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("Fail to get new array ");
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
- (jint)num_items, (jint)depth);
+ (jint)num_items, (jint)depth, addr.get());
}
static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
@@ -575,8 +685,17 @@
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("Fail to get new array ");
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr.address);
+
sCallbackEnv->CallVoidMethod(
- sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status);
+ sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status, addr.get());
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -598,6 +717,12 @@
btavrcp_set_browsed_player_callback,
btavrcp_set_addressed_player_callback};
+static btrc_vendor_ctrl_callbacks_t sBluetoothAvrcpVendorCallbacks = {
+ sizeof(sBluetoothAvrcpVendorCallbacks),
+ btavrcp_get_vendor_rcfeatures_callback,
+ btavrcp_vendor_get_mediaelementattribute_rsp_callback,
+};
+
static void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
env->GetMethodID(clazz, "handlePassthroughRsp", "(II[B)V");
@@ -608,7 +733,7 @@
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(ZZ[B)V");
- method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
+ method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BII)V");
method_setplayerappsettingrsp =
env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");
@@ -628,6 +753,9 @@
method_handletrackchanged =
env->GetMethodID(clazz, "onTrackChanged", "([BB[I[Ljava/lang/String;)V");
+ method_handleElementAttrupdate =
+ env->GetMethodID(clazz, "onElementAttributeUpdate", "([BB[I[Ljava/lang/String;)V");
+
method_handleplaypositionchanged =
env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
@@ -636,10 +764,10 @@
method_handleGetFolderItemsRsp =
env->GetMethodID(clazz, "handleGetFolderItemsRsp",
- "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V");
+ "(I[Landroid/media/browse/MediaBrowser$MediaItem;[B)V");
method_handleGetPlayerItemsRsp = env->GetMethodID(
clazz, "handleGetPlayerItemsRsp",
- "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+ "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;[B)V");
method_createFromNativeMediaItem =
env->GetMethodID(clazz, "createFromNativeMediaItem",
@@ -653,11 +781,11 @@
"(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
"avrcpcontroller/AvrcpPlayer;");
method_handleChangeFolderRsp =
- env->GetMethodID(clazz, "handleChangeFolderRsp", "(I)V");
+ env->GetMethodID(clazz, "handleChangeFolderRsp", "(I[B)V");
method_handleSetBrowsedPlayerRsp =
- env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
+ env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II[B)V");
method_handleSetAddressedPlayerRsp =
- env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)V");
+ env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I[B)V");
ALOGI("%s: succeeds", __func__);
}
@@ -676,6 +804,12 @@
return;
}
+ if (sBluetoothAvrcpVendorInterface != NULL) {
+ ALOGW("Cleaning up Avrcp Vendor Interface before initializing...");
+ sBluetoothAvrcpVendorInterface->cleanup_vendor();
+ sBluetoothAvrcpVendorInterface = NULL;
+ }
+
if (sBluetoothAvrcpInterface != NULL) {
ALOGW("Cleaning up Avrcp Interface before initializing...");
sBluetoothAvrcpInterface->cleanup();
@@ -705,6 +839,28 @@
return;
}
+ sBluetoothAvrcpVendorInterface =
+ (btrc_vendor_ctrl_interface_t*)btInf->get_profile_interface(
+ BT_PROFILE_AV_RC_VENDOR_CTRL_ID);
+ if (sBluetoothAvrcpVendorInterface == NULL) {
+ ALOGE("Failed to get Bluetooth Avrcp Vendor Controller Interface");
+ sBluetoothAvrcpInterface->cleanup();
+ sBluetoothAvrcpInterface = NULL;
+ return;
+ }
+
+ status =
+ sBluetoothAvrcpVendorInterface->init_vendor(&sBluetoothAvrcpVendorCallbacks);
+ if (status != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to initialize Bluetooth Avrcp Vendor Controller, status: %d",
+ status);
+ sBluetoothAvrcpVendorInterface = NULL;
+ sBluetoothAvrcpInterface->cleanup();
+ sBluetoothAvrcpInterface = NULL;
+ return;
+ }
+
+
sCallbacksObj = env->NewGlobalRef(object);
}
@@ -715,6 +871,11 @@
return;
}
+ if (sBluetoothAvrcpVendorInterface != NULL) {
+ sBluetoothAvrcpVendorInterface->cleanup_vendor();
+ sBluetoothAvrcpVendorInterface = NULL;
+ }
+
if (sBluetoothAvrcpInterface != NULL) {
sBluetoothAvrcpInterface->cleanup();
sBluetoothAvrcpInterface = NULL;
@@ -1054,6 +1215,49 @@
env->ReleaseByteArrayElements(address, addr, 0);
}
+/* This api is used to fetch metadata for currently playing track
+ * num_attribs: number of attributes to be fetched. 0 corresponds to fetch all
+ * attributes
+ * attrib_ids: list of attributes we want to fetch. NULL corresponds to fetch
+ * all attributes.
+ */
+ static void getElementAttributesNative(JNIEnv *env, jobject object, jbyteArray address,
+ jbyte num_attribs, jbyteArray attrib_ids) {
+ if (!sBluetoothAvrcpVendorInterface) return;
+ bt_status_t status;
+ jbyte *addr;
+ uint32_t *pAttrs = NULL;
+ jbyte *attr;
+ int i;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ if (num_attribs == 0) {
+ // we have to fetch all element attributes
+ sBluetoothAvrcpVendorInterface->get_media_element_attributes_vendor((RawAddress *)addr,
+ (uint8_t)num_attribs, NULL);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return;
+ }
+ pAttrs = new uint32_t[num_attribs];
+ attr = env->GetByteArrayElements(attrib_ids, NULL);
+ for (i = 0; i < num_attribs; ++i) {
+ pAttrs[i] = (uint32_t)attr[i];
+ }
+ status = sBluetoothAvrcpVendorInterface->get_media_element_attributes_vendor(
+ (RawAddress *)addr, (uint8_t)num_attribs, pAttrs);
+ if (status != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending getElementAttributesNative command, status: %d", status);
+ }
+ delete[] pAttrs;
+ env->ReleaseByteArrayElements(address, addr, 0);
+ env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+ }
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "()V", (void*)initNative},
@@ -1075,6 +1279,7 @@
{"playItemNative", "([BB[BI)V", (void*)playItemNative},
{"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
{"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
+ {"getElementAttributesNative", "([BB[B)V",(void *) getElementAttributesNative},
};
int register_com_android_bluetooth_avrcp_controller(JNIEnv* env) {
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 976e388..1008aeb 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -70,12 +70,12 @@
jmethodID constructor;
} android_bluetooth_UidTraffic;
-static const bt_interface_t* sBluetoothInterface = NULL;
-static const btsock_interface_t* sBluetoothSocketInterface = NULL;
-static JNIEnv* callbackEnv = NULL;
+static const bt_interface_t *sBluetoothInterface = NULL;
+static const btsock_interface_t *sBluetoothSocketInterface = NULL;
+static JNIEnv *callbackEnv = NULL;
-static jobject sJniAdapterServiceObj;
-static jobject sJniCallbacksObj;
+static jobject sJniAdapterServiceObj = NULL;
+static jobject sJniCallbacksObj = NULL;
static jfieldID sJniCallbacksField;
namespace {
@@ -510,10 +510,12 @@
jint status_;
};
+#ifdef ENABLE_JAVA_WAKE_LOCKS
static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake,
alarm_cb cb, void* data) {
JNIThreadAttacher attacher;
JNIEnv* env = attacher.getEnv();
+ jboolean ret = JNI_FALSE;
if (env == nullptr) {
ALOGE("%s: Unable to get JNI Env", __func__);
@@ -524,10 +526,15 @@
sAlarmCallbackData = data;
jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
- jboolean ret =
- env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
+ if (sJniAdapterServiceObj) {
+ ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
(jlong)delay_millis, jshould_wake);
+ } else {
+ ALOGE("%s JNI ERROR : JNI reference already cleaned : set_wake_alarm_callout", __func__);
+ }
+
if (!ret) {
+ ALOGE("%s setWakeAlarm failed:ret= %d ", __func__, ret);
sAlarmCallback = NULL;
sAlarmCallbackData = NULL;
}
@@ -548,9 +555,13 @@
{
ScopedLocalRef<jstring> lock_name_jni(env, env->NewStringUTF(lock_name));
if (lock_name_jni.get()) {
- bool acquired = env->CallBooleanMethod(
- sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni.get());
- if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
+ if (sJniAdapterServiceObj) {
+ bool acquired = env->CallBooleanMethod(
+ sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni.get());
+ if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
+ } else {
+ ALOGE("%s JNI ERROR : JNI reference already cleaned : acquire_wake_lock_callout", __func__);
+ }
} else {
ALOGE("%s unable to allocate string: %s", __func__, lock_name);
ret = BT_STATUS_NOMEM;
@@ -573,9 +584,13 @@
{
ScopedLocalRef<jstring> lock_name_jni(env, env->NewStringUTF(lock_name));
if (lock_name_jni.get()) {
- bool released = env->CallBooleanMethod(
- sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni.get());
- if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
+ if (sJniAdapterServiceObj) {
+ bool released = env->CallBooleanMethod(
+ sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni.get());
+ if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
+ } else {
+ ALOGE("%s JNI ERROR : JNI reference already cleaned : release_wake_lock_callout", __func__);
+ }
} else {
ALOGE("%s unable to allocate string: %s", __func__, lock_name);
ret = BT_STATUS_NOMEM;
@@ -584,7 +599,7 @@
return ret;
}
-
+#endif
// Called by Java code when alarm is fired. A wake lock is held by the caller
// over the duration of this callback.
static void alarmFiredNative(JNIEnv* env, jobject obj) {
@@ -594,11 +609,12 @@
ALOGE("%s() - Alarm fired with callback not set!", __func__);
}
}
-
+#ifdef ENABLE_JAVA_WAKE_LOCKS
static bt_os_callouts_t sBluetoothOsCallouts = {
sizeof(sBluetoothOsCallouts), set_wake_alarm_callout,
acquire_wake_lock_callout, release_wake_lock_callout,
};
+#endif
#define PROPERTY_BT_LIBRARY_NAME "ro.bluetooth.library_name"
#define DEFAULT_BT_LIBRARY_NAME "libbluetooth.so"
@@ -700,11 +716,14 @@
}
int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
- if (ret != BT_STATUS_SUCCESS) {
+ if (ret != BT_STATUS_SUCCESS && ret != BT_STATUS_DONE) {
ALOGE("Error while setting the callbacks: %d\n", ret);
sBluetoothInterface = NULL;
return JNI_FALSE;
}
+
+ /*disable these os_callout settings, so that native wake_lock will be enabled*/
+#ifdef ENABLE_JAVA_WAKE_LOCKS
ret = sBluetoothInterface->set_os_callouts(&sBluetoothOsCallouts);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("Error while setting Bluetooth callouts: %d\n", ret);
@@ -712,6 +731,7 @@
sBluetoothInterface = NULL;
return JNI_FALSE;
}
+#endif
sBluetoothSocketInterface =
(btsock_interface_t*)sBluetoothInterface->get_profile_interface(
@@ -1161,6 +1181,11 @@
const char** args = nullptr;
if (numArgs > 0) args = new const char*[numArgs];
+ if (!args || !argObjs) {
+ ALOGE("%s: not have enough memeory", __func__);
+ return;
+ }
+
for (int i = 0; i < numArgs; i++) {
argObjs[i] = (jstring)env->GetObjectArrayElement(argArray, i);
args[i] = env->GetStringUTFChars(argObjs[i], NULL);
@@ -1264,17 +1289,17 @@
/*
* JNI Initialization
*/
-jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
- JNIEnv* e;
- int status;
+jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
+ JNIEnv *e;
+ int status;
- ALOGV("Bluetooth Adapter Service : loading JNI\n");
+ ALOGV("Bluetooth Adapter Service : loading JNI\n");
- // Check JNI version
- if (jvm->GetEnv((void**)&e, JNI_VERSION_1_6)) {
- ALOGE("JNI version mismatch error");
- return JNI_ERR;
- }
+ // Check JNI version
+ if (jvm->GetEnv((void **)&e, JNI_VERSION_1_6)) {
+ ALOGE("JNI version mismatch error");
+ return JNI_ERR;
+ }
status = android::register_com_android_bluetooth_btservice_AdapterService(e);
if (status < 0) {
@@ -1300,6 +1325,12 @@
return JNI_ERR;
}
+ status = android::register_com_android_bluetooth_ba(e);
+ if (status < 0) {
+ ALOGE("jni BA Transmitter registration failure: %d", status);
+ return JNI_ERR;
+ }
+
status = android::register_com_android_bluetooth_a2dp_sink(e);
if (status < 0) {
ALOGE("jni a2dp sink registration failure: %d", status);
@@ -1359,11 +1390,29 @@
return JNI_ERR;
}
+ status = android::register_com_android_bluetooth_btservice_vendor(e);
+ if (status < 0) {
+ ALOGE("jni vendor registration failure: %d", status);
+ return JNI_ERR;
+ }
+
+ status = android::register_com_android_bluetooth_btservice_vendor_socket(e);
+ if (status < 0) {
+ ALOGE("jni vendor socket registration failure: %d", status);
+ return JNI_ERR;
+ }
+
status = android::register_com_android_bluetooth_hearing_aid(e);
if (status < 0) {
ALOGE("jni hearing aid registration failure: %d", status);
return JNI_ERR;
}
+ status = android::register_com_android_bluetooth_avrcp_ext(e);
+ if (status < 0) {
+ ALOGE("jni avrcp_ext registration failure: %d", status);
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_6;
}
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 86e667b..79726c2 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -1165,11 +1165,24 @@
}
static void gattSetScanParametersNative(JNIEnv* env, jobject object,
- jint client_if, jint scan_interval_unit,
- jint scan_window_unit) {
+ jint client_if, jint scan_phy,
+ jintArray scan_interval_unit,
+ jintArray scan_window_unit) {
+ std::vector<uint32_t> scan_interval = {0,0};
+ std::vector<uint32_t> scan_window = {0,0};
if (!sGattIf) return;
+
+ int scan_int_cnt = env->GetArrayLength(scan_interval_unit);
+ if(scan_int_cnt > 0) {
+ env->GetIntArrayRegion(scan_interval_unit, 0, scan_int_cnt, (jint *)&scan_interval[0]);
+ }
+
+ int scan_window_cnt = env->GetArrayLength(scan_window_unit);
+ if(scan_window_cnt > 0) {
+ env->GetIntArrayRegion(scan_window_unit, 0, scan_window_cnt, (jint *)&scan_window[0]);
+ }
sGattIf->scanner->SetScanParameters(
- scan_interval_unit, scan_window_unit,
+ scan_phy, scan_interval, scan_window,
base::Bind(&set_scan_params_cmpl_cb, client_if));
}
@@ -2128,7 +2141,7 @@
(void*)gattClientScanFilterClearNative},
{"gattClientScanFilterEnableNative", "(IZ)V",
(void*)gattClientScanFilterEnableNative},
- {"gattSetScanParametersNative", "(III)V",
+ {"gattSetScanParametersNative", "(II[I[I)V",
(void*)gattSetScanParametersNative},
};
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 8547aa6..4b01d8d 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -446,6 +446,7 @@
if (!sBluetoothHfpInterface) {
ALOGW("%s: Failed to get Bluetooth Handsfree Interface", __func__);
jniThrowIOException(env, EINVAL);
+ return;
}
bt_status_t status =
sBluetoothHfpInterface->Init(JniHeadsetCallbacks::GetInstance(),
diff --git a/jni/com_android_bluetooth_hid_host.cpp b/jni/com_android_bluetooth_hid_host.cpp
index 7838ff6..b8f4d65 100644
--- a/jni/com_android_bluetooth_hid_host.cpp
+++ b/jni/com_android_bluetooth_hid_host.cpp
@@ -24,7 +24,7 @@
#include "utils/Log.h"
#include <string.h>
-
+#include <shared_mutex>
namespace android {
static jmethodID method_onConnectStateChanged;
@@ -36,6 +36,7 @@
static const bthh_interface_t* sBluetoothHidInterface = NULL;
static jobject mCallbacksObj = NULL;
+static std::shared_timed_mutex mCallbacks_mutex;
static jbyteArray marshall_bda(RawAddress* bd_addr) {
CallbackEnv sCallbackEnv(__func__);
@@ -53,6 +54,7 @@
static void connection_state_callback(RawAddress* bd_addr,
bthh_connection_state_t state) {
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -72,6 +74,7 @@
static void get_protocol_mode_callback(RawAddress* bd_addr,
bthh_status_t hh_status,
bthh_protocol_mode_t mode) {
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -95,6 +98,7 @@
static void get_report_callback(RawAddress* bd_addr, bthh_status_t hh_status,
uint8_t* rpt_data, int rpt_size) {
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -126,6 +130,7 @@
static void virtual_unplug_callback(RawAddress* bd_addr,
bthh_status_t hh_status) {
ALOGV("call to virtual_unplug_callback");
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -142,6 +147,7 @@
}
static void handshake_callback(RawAddress* bd_addr, bthh_status_t hh_status) {
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
@@ -160,6 +166,7 @@
static void get_idle_time_callback(RawAddress* bd_addr, bthh_status_t hh_status,
int idle_time) {
+ std::shared_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
@@ -198,6 +205,7 @@
}
static void initializeNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -234,6 +242,7 @@
}
static void cleanupNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(mCallbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index b959858..5040bdc 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -301,12 +301,12 @@
* E.g. as a mapping for them such that the naming will match the underlying
* matching folder ID's.
*/
- public static final String FOLDER_NAME_INBOX = "INBOX";
- public static final String FOLDER_NAME_SENT = "SENT";
- public static final String FOLDER_NAME_OUTBOX = "OUTBOX";
- public static final String FOLDER_NAME_DRAFT = "DRAFT";
- public static final String FOLDER_NAME_DELETED = "DELETED";
- public static final String FOLDER_NAME_OTHER = "OTHER";
+ public static final String FOLDER_NAME_INBOX = "inbox";
+ public static final String FOLDER_NAME_SENT = "sent";
+ public static final String FOLDER_NAME_OUTBOX = "outbox";
+ public static final String FOLDER_NAME_DRAFT = "draft";
+ public static final String FOLDER_NAME_DELETED = "deleted";
+ public static final String FOLDER_NAME_OTHER = "other";
/**
* Folder IDs to be used with Instant Messaging virtual folders
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 8440c77..5a8fb6e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -133,4 +133,8 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"蓝牙音频"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"无法传输 4GB 以上的文件"</string>
+ <!-- Bluetooth AVRCP Browsing Dialog-->
+ <string name="bluetooth_rc_feat_title">"蓝牙音频浏览"</string>
+ <string name="bluetooth_rc_feat_content">"对端支持该先进功能"</string>
+ <string name="bluetooth_rc_feat_subtext">"重新配对对端设备使其支持该功能"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 4220c84..66e7d2d 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -133,4 +133,7 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移大於 4GB 的檔案"</string>
+ <string name="bluetooth_rc_feat_title">"藍芽音頻瀏覽"</string>
+ <string name="bluetooth_rc_feat_content">"對端支持該先進功能"</string>
+ <string name="bluetooth_rc_feat_subtext">"重新配對對端設備使其支持該功能"</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9301e9d..cf2ceef 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -33,6 +33,7 @@
<bool name="profile_supported_mapmce">false</bool>
<bool name="profile_supported_hid_device">true</bool>
<bool name="profile_supported_hearing_aid">false</bool>
+ <bool name="profile_supported_ba">false</bool>
<!-- If true, we will require location to be enabled on the device to
fire Bluetooth LE scan result callbacks in addition to having one
@@ -88,9 +89,11 @@
value should be unique. -->
<integer name="a2dp_source_codec_priority_sbc">1001</integer>
<integer name="a2dp_source_codec_priority_aac">2001</integer>
- <integer name="a2dp_source_codec_priority_aptx">3001</integer>
- <integer name="a2dp_source_codec_priority_aptx_hd">4001</integer>
- <integer name="a2dp_source_codec_priority_ldac">5001</integer>
+ <integer name="a2dp_source_codec_priority_ldac">3001</integer>
+ <integer name="a2dp_source_codec_priority_aptx">4001</integer>
+ <integer name="a2dp_source_codec_priority_aptx_hd">5001</integer>
+ <integer name="a2dp_source_codec_priority_aptx_adaptive">6001</integer>
+ <integer name="a2dp_source_codec_priority_aptx_tws">7001</integer>
<!-- Package that is responsible for user interaction on pairing request,
success or cancel.
@@ -105,4 +108,16 @@
<!-- Flag whether or not to keep polling AG with CLCC for call information every 2 seconds -->
<bool name="hfp_clcc_poll_during_call">true</bool>
+ <!-- Reload supported Bluetooth Profiles while BLE is turning ON -->
+ <bool name="reload_supported_profiles_when_enabled">true</bool>
+ <!-- For AVRCP cover art configuration If there is no update from UI
+ these default values would be used to fetch cover art.
+ height and width are to be mentioned in pixels
+ maxsize is to be mentioned in bytes -->
+ <string name="avrcp_cover_art_default_mimetype">JPEG</string>
+ <string name="avrcp_cover_art_default_image_type">image</string>
+ <integer name="avrcp_cover_art_default_height">500</integer>
+ <integer name="avrcp_cover_art_default_width">500</integer>
+ <integer name="avrcp_cover_art_default_maxsize">200000</integer>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 17023e4..dcb992b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -248,4 +248,10 @@
<string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label">Bluetooth Audio</string>
<string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string>
+ <!-- Bluetooth AVRCP Browsing Dialog and Notification-->
+ <string name="avrcp_notification_name">BT ADVANCE FEATURE AVRCP</string>
+ <string name="bluetooth_advanced_feat_description">Bluetooth Advanced Browsing Feature</string>
+ <string name="bluetooth_rc_feat_title">Bluetooth Media Browsing</string>
+ <string name="bluetooth_rc_feat_content">Peer supports advanced feature</string>
+ <string name="bluetooth_rc_feat_subtext">Re-pair from peer to enable it</string>
</resources>
diff --git a/src/com/android/bluetooth/ObexServerSockets.java b/src/com/android/bluetooth/ObexServerSockets.java
index 789f12f..4991038 100644
--- a/src/com/android/bluetooth/ObexServerSockets.java
+++ b/src/com/android/bluetooth/ObexServerSockets.java
@@ -81,19 +81,33 @@
}
/**
- * Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
- * {@link BluetoothServerSocket}
+ * Creates a fixed Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
+ * {@link BluetoothServerSocket}
* @param validator a reference to the {@link IObexConnectionHandler} object to call
* to validate an incoming connection.
+ * @param rfcommChannel is a fixed RFCOMM channnel to allocate
+ * @param l2capPsm is a fixed L2CAP PSM to allocate
* @return a reference to a {@link ObexServerSockets} object instance.
* @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
*/
- public static ObexServerSockets createInsecure(IObexConnectionHandler validator) {
- return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
- BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
+ public static ObexServerSockets createInsecureWithFixedChannels(IObexConnectionHandler
+ validator, int rfcommChannel, int l2capPsm) {
+ return create(validator, rfcommChannel, l2capPsm , false);
}
-
private static final int CREATE_RETRY_TIME = 10;
+ /**
+ * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
+ * @param validator a reference to the {@link IObexConnectionHandler} object to call
+ * to validate an incoming connection.
+ * @param rfcommChannel fixed rfcomm channel number to listen on
+ * @param l2capPsm fixed l2cap psm to listen on
+ * @return a reference to a {@link ObexServerSockets} object instance.
+ * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
+ */
+ public static ObexServerSockets createWithFixedChannels(IObexConnectionHandler validator,
+ int rfcommChannel, int l2capPsm) {
+ return create(validator, rfcommChannel, l2capPsm, true);
+ }
/**
* Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
@@ -126,14 +140,14 @@
for (int i = 0; i < CREATE_RETRY_TIME; i++) {
initSocketOK = true;
try {
- if (rfcommSocket == null) {
+ if (rfcommSocket == null && rfcommChannel != -1) {
if (isSecure) {
rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
} else {
rfcommSocket = bt.listenUsingInsecureRfcommOn(rfcommChannel);
}
}
- if (l2capSocket == null) {
+ if (l2capSocket == null && l2capPsm != -1) {
if (isSecure) {
l2capSocket = bt.listenUsingL2capOn(l2capPsm);
} else {
@@ -143,6 +157,10 @@
} catch (IOException e) {
Log.e(STAG, "Error create ServerSockets ", e);
initSocketOK = false;
+ } catch (SecurityException e) {
+ Log.e(STAG, "Error create ServerSockets ", e);
+ initSocketOK = false;
+ break;
}
if (!initSocketOK) {
// Need to break out of this loop if BT is being turned off.
@@ -205,12 +223,15 @@
if (D) {
Log.d(mTag, "startAccept()");
}
+ if (mRfcommSocket != null) {
+ mRfcommThread = new SocketAcceptThread(mRfcommSocket);
+ mRfcommThread.start();
+ }
- mRfcommThread = new SocketAcceptThread(mRfcommSocket);
- mRfcommThread.start();
-
- mL2capThread = new SocketAcceptThread(mL2capSocket);
- mL2capThread.start();
+ if (mL2capSocket != null) {
+ mL2capThread = new SocketAcceptThread(mL2capSocket);
+ mL2capThread.start();
+ }
}
/**
@@ -221,7 +242,10 @@
*/
private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
if (D) {
- Log.d(mTag, "onConnect() socket: " + conSocket);
+ Log.d(mTag, "onConnect() socket: " + conSocket + " mConHandler " + mConHandler);
+ }
+ if (mConHandler == null) {
+ return false;
}
return mConHandler.onConnect(device, conSocket);
}
@@ -232,7 +256,8 @@
private synchronized void onAcceptFailed() {
shutdown(false);
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
- if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
+ if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)
+ && mConHandler != null) {
Log.d(mTag, "onAcceptFailed() calling shutdown...");
mConHandler.onAcceptFailed();
}
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 457b053..bb6bf6c 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -64,6 +64,7 @@
}
public static byte[] getByteAddress(BluetoothDevice device) {
+ if (device == null) return new byte[BD_ADDR_LEN];
return getBytesFromAddress(device.getAddress());
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..b5fc3a1 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -21,9 +21,9 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
-
+import android.os.SystemProperties;
import com.android.bluetooth.R;
-
+import com.android.bluetooth.btservice.AdapterService;
/*
* A2DP Codec Configuration setup.
*/
@@ -39,8 +39,10 @@
private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private int mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-
+ private int mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ //private int SOURCE_CODEC_TYPE_APTX_TWS = BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + 1;
A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
mContext = context;
mA2dpNativeInterface = a2dpNativeInterface;
@@ -100,6 +102,7 @@
}
int value;
+ AdapterService mAdapterService = AdapterService.getAdapterService();
try {
value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
} catch (NotFoundException e) {
@@ -129,61 +132,113 @@
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityAptx = value;
}
-
- try {
- value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
- } catch (NotFoundException e) {
- value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- }
- if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
- < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
- mA2dpSourceCodecPriorityAptxHd = value;
+ if(mAdapterService.isSplitA2DPSourceAPTXADAPTIVE()) {
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_adaptive);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ mA2dpSourceCodecPriorityAptxAdaptive = value;
+ }
+ } else {
+ mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
- try {
- value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
- } catch (NotFoundException e) {
- value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ if(mAdapterService.isSplitA2DPSourceAPTXHD()) {
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ mA2dpSourceCodecPriorityAptxHd = value;
+ }
+ } else {
+ mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
- if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
- < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
- mA2dpSourceCodecPriorityLdac = value;
+
+
+ if(mAdapterService.isSplitA2DPSourceLDAC()) {
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ mA2dpSourceCodecPriorityLdac = value;
+ }
+ } else {
+ mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
+ if (mAdapterService.isVendorIntfEnabled()) {
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_tws);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ mA2dpSourceCodecPriorityAptxTwsp = value;
+ }
+ } else {
+ mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
BluetoothCodecConfig codecConfig;
- BluetoothCodecConfig[] codecConfigArray =
+ BluetoothCodecConfig[] codecConfigArray;
+ int codecCount = 0;
+ codecConfigArray =
new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+
codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
- codecConfigArray[0] = codecConfig;
+ codecConfigArray[codecCount++] = codecConfig;
codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
- codecConfigArray[1] = codecConfig;
+ codecConfigArray[codecCount++] = codecConfig;
codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
- codecConfigArray[2] = codecConfig;
+ codecConfigArray[codecCount++] = codecConfig;
codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
- codecConfigArray[3] = codecConfig;
+ codecConfigArray[codecCount++] = codecConfig;
codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
- codecConfigArray[4] = codecConfig;
+ codecConfigArray[codecCount++] = codecConfig;
+ if (mAdapterService.isVendorIntfEnabled()) {
+ codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_TWSP,
+ mA2dpSourceCodecPriorityAptxTwsp, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+ .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+ 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+ codecConfigArray[codecCount++] = codecConfig;
+ }
+ codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE,
+ mA2dpSourceCodecPriorityAptxAdaptive, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+ .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+ 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+ codecConfigArray[codecCount++] = codecConfig;
return codecConfigArray;
}
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 64a9cad..5c44e02 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -18,6 +18,7 @@
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
@@ -34,14 +35,20 @@
import android.support.annotation.GuardedBy;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import android.os.SystemProperties;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
-import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.avrcp.Avrcp;
+import com.android.bluetooth.avrcp.Avrcp_ext;
import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.ba.BATService;
+import android.os.SystemClock;
+import com.android.bluetooth.gatt.GattService;
import java.util.ArrayList;
import java.util.List;
@@ -59,11 +66,17 @@
private static final String TAG = "A2dpService";
private static A2dpService sA2dpService;
+ private static A2dpSinkService sA2dpSinkService;
+ private static boolean mA2dpSrcSnkConcurrency;
private BluetoothAdapter mAdapter;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
private Avrcp mAvrcp;
+ private Avrcp_ext mAvrcp_ext;
+ private final Object mBtA2dpLock = new Object();
+ private final Object mBtAvrcpLock = new Object();
+ private final Object mActiveDeviceLock = new Object();
@VisibleForTesting
A2dpNativeInterface mA2dpNativeInterface;
@@ -74,16 +87,52 @@
private BluetoothDevice mActiveDevice;
private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
new ConcurrentHashMap<>();
+ private static final int[] CONNECTING_CONNECTED_STATES = {
+ BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED
+ };
+ // A2DP disconnet will be delayed at audioservice,
+ // follwoing flags capture delay and delay time.
+ private int mDisconnectDelay = 0;
+ private long mDisconnectTime = 0;
// Upper limit of all A2DP devices: Bonded or Connected
private static final int MAX_A2DP_STATE_MACHINES = 50;
// Upper limit of all A2DP devices that are Connected or Connecting
private int mMaxConnectedAudioDevices = 1;
+ private int mSetMaxConnectedAudioDevices = 1;
// A2DP Offload Enabled in platform
boolean mA2dpOffloadEnabled = false;
-
+ private boolean disconnectExisting = false;
+ private int EVENT_TYPE_NONE = 0;
+ private int mA2dpStackEvent = EVENT_TYPE_NONE;
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
+ private boolean mIsTwsPlusEnabled = false;
+ private boolean mIsTwsPlusMonoSupported = false;
+ private String mTwsPlusChannelMode = "dual-mono";
+ private BluetoothDevice mDummyDevice = null;
+
+ private static final long AptxBLEScanMask = 0x3000;
+ private static final long Aptx_BLEScanEnable = 0x1000;
+ private static final long Aptx_BLEScanDisable = 0x2000;
+ private static final int SET_EBMONO_CFG = 1;
+ private static final int MonoCfg_Timeout = 5000;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg)
+ {
+ switch (msg.what) {
+ case SET_EBMONO_CFG:
+ Log.d(TAG, "setparameters to Mono");
+ mAudioManager.setParameters("TwsChannelConfig=mono");
+ mTwsPlusChannelMode = "mono";
+ break;
+ default:
+ break;
+ }
+ }
+ };
@Override
protected IProfileServiceBinder initBinder() {
@@ -99,7 +148,8 @@
protected boolean start() {
Log.i(TAG, "start()");
if (sA2dpService != null) {
- throw new IllegalStateException("start() called twice");
+ Log.w(TAG, "A2dpService is already running");
+ return true;
}
// Step 1: Get BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager.
@@ -116,10 +166,36 @@
// Step 2: Get maximum number of connected audio devices
mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
+ mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+ if (mAdapterService.isVendorIntfEnabled()) {
+ String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+ if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
+ mIsTwsPlusEnabled = true;
+ }
+ Log.i(TAG, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
+ if (mIsTwsPlusEnabled) {
+ mMaxConnectedAudioDevices = 2;
+ } else if (mMaxConnectedAudioDevices > 2) {
+ mMaxConnectedAudioDevices = 2;
+ mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+ }
+ String twsPlusMonoEnabled = SystemProperties.get("persist.vendor.btstack.twsplus.monosupport");
+ if (!twsPlusMonoEnabled.isEmpty() && "true".equals(twsPlusMonoEnabled)) {
+ mIsTwsPlusMonoSupported = true;
+ }
+ String TwsPlusChannelMode = SystemProperties.get("persist.vendor.btstack.twsplus.defaultchannelmode");
+ if (!TwsPlusChannelMode.isEmpty() && "mono".equals(TwsPlusChannelMode)) {
+ mTwsPlusChannelMode = "mono";
+ }
+ Log.d(TAG, "Default TwsPlus ChannelMode: " + mTwsPlusChannelMode);
+ }
Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
// Step 3: Setup AVRCP
- mAvrcp = Avrcp.make(this);
+ if(mAdapterService.isVendorIntfEnabled())
+ mAvrcp_ext = Avrcp_ext.make(this, this, mMaxConnectedAudioDevices);
+ else
+ mAvrcp = Avrcp.make(this);
// Step 4: Start handler thread for state machines
mStateMachines.clear();
@@ -138,6 +214,11 @@
if (DBG) {
Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
}
+ mA2dpSrcSnkConcurrency= SystemProperties.getBoolean(
+ "persist.vendor.service.bt.a2dp_concurrency", false);
+ if (DBG) {
+ Log.d(TAG, "A2DP concurrency set to " + mA2dpSrcSnkConcurrency);
+ }
// Step 8: Setup broadcast receivers
IntentFilter filter = new IntentFilter();
@@ -170,6 +251,8 @@
if (mActiveDevice != null && AvrcpTargetService.get() != null) {
AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
}
+ if (mActiveDevice != null && mAvrcp_ext != null)
+ mAvrcp_ext.storeVolumeForDevice(mActiveDevice);
// Step 9: Clear active device and stop playing audio
removeActiveDevice(true);
@@ -191,7 +274,7 @@
mA2dpCodecConfig = null;
// Step 4: Destroy state machines and stop handler thread
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
for (A2dpStateMachine sm : mStateMachines.values()) {
sm.doQuit();
sm.cleanup();
@@ -202,13 +285,30 @@
mStateMachinesThread = null;
// Step 3: Cleanup AVRCP
- mAvrcp.doQuit();
- mAvrcp.cleanup();
- mAvrcp = null;
+ synchronized (mBtAvrcpLock) {
+ if(mAvrcp_ext != null) {
+ mAvrcp_ext.doQuit();
+ mAvrcp_ext.cleanup();
+ Avrcp_ext.clearAvrcpInstance();
+ mAvrcp_ext = null;
+ } else if(mAvrcp != null) {
+ mAvrcp.doQuit();
+ mAvrcp.cleanup();
+ mAvrcp = null;
+ }
+ }
// Step 2: Reset maximum number of connected audio devices
- mMaxConnectedAudioDevices = 1;
-
+ if (mAdapterService.isVendorIntfEnabled()) {
+ if (mIsTwsPlusEnabled) {
+ mMaxConnectedAudioDevices = 2;
+ } else {
+ mMaxConnectedAudioDevices = 1;
+ }
+ } else {
+ mMaxConnectedAudioDevices = 1;
+ }
+ mSetMaxConnectedAudioDevices = 1;
// Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
mAudioManager = null;
mA2dpNativeInterface = null;
@@ -258,28 +358,49 @@
return false;
}
- synchronized (mStateMachines) {
- if (!connectionAllowedCheckMaxDevices(device)) {
+ synchronized (mBtA2dpLock) {
+ disconnectExisting = false;
+ if (!connectionAllowedCheckMaxDevices(device) && !disconnectExisting) {
Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
return false;
}
+ if (disconnectExisting) {
+ disconnectExisting = false;
+ //Log.e(TAG,"Disconnect existing connections");
+ List <BluetoothDevice> connectingConnectedDevices =
+ getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ Log.e(TAG,"Disconnect existing connections = " + connectingConnectedDevices.size());
+ for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {
+ Log.d(TAG,"calling disconnect to " + connectingConnectedDevice);
+ disconnect(connectingConnectedDevice);
+ }
+ }
A2dpStateMachine smConnect = getOrCreateStateMachine(device);
if (smConnect == null) {
Log.e(TAG, "Cannot connect to " + device + " : no state machine");
return false;
}
+ if (mA2dpSrcSnkConcurrency) {
+ sA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ List<BluetoothDevice> srcDevs = sA2dpSinkService.getConnectedDevices();
+ for ( BluetoothDevice src : srcDevs ) {
+ Log.d(TAG, "calling sink disconnect to " + src);
+ sA2dpSinkService.disconnect(src);
+ }
+ }
+
smConnect.sendMessage(A2dpStateMachine.CONNECT);
return true;
}
}
- boolean disconnect(BluetoothDevice device) {
+ public boolean disconnect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "disconnect(): " + device);
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
@@ -292,7 +413,7 @@
public List<BluetoothDevice> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
List<BluetoothDevice> devices = new ArrayList<>();
for (A2dpStateMachine sm : mStateMachines.values()) {
if (sm.isConnected()) {
@@ -302,7 +423,59 @@
return devices;
}
}
+ private boolean isConnectionAllowed(BluetoothDevice device, boolean tws_connected,
+ int num_connected) {
+ if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
+ Log.d(TAG, "No TWSPLUS connections as It is not Enabled");
+ return false;
+ }
+ if (num_connected == 0) return true;
+ List <BluetoothDevice> connectingConnectedDevices =
+ getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ BluetoothDevice mConnDev = null;
+ if(!connectingConnectedDevices.isEmpty())
+ mConnDev = connectingConnectedDevices.get(0);
+ if (mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING ||
+ mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED) {
+ if ((!mAdapterService.isTwsPlusDevice(device) && tws_connected) ||
+ (mAdapterService.isTwsPlusDevice(device) && !tws_connected)) {
+ Log.d(TAG,"isConnectionAllowed: incoming connection not allowed");
+ mA2dpStackEvent = EVENT_TYPE_NONE;
+ return false;
+ }
+ }
+ if (num_connected > 1 &&
+ ((!tws_connected && mAdapterService.isTwsPlusDevice(device)) ||
+ (tws_connected && !mAdapterService.isTwsPlusDevice(device)))) {
+ Log.d(TAG,"isConnectionAllowed: Max connections reached");
+ return false;
+ }
+ if (!tws_connected && mAdapterService.isTwsPlusDevice(device)) {
+ Log.d(TAG,"isConnectionAllowed: Disconnect legacy device for outgoing TWSP connection");
+ disconnectExisting = true;
+ return false;
+ }
+ if (tws_connected && mAdapterService.isTwsPlusDevice(device)) {
+ //if (num_connected == mMaxConnectedAudioDevices) {
+ if (num_connected > 1) {
+ Log.d(TAG,"isConnectionAllowed: Max TWS connected, disconnect first");
+ return false;
+ } else if(mConnDev != null && mAdapterService.getTwsPlusPeerAddress(mConnDev).equals(device.getAddress())) {
+ Log.d(TAG,"isConnectionAllowed: Peer earbud pair allow connection");
+ return true;
+ } else {
+ Log.d(TAG,"isConnectionAllowed: Unpaired earbud, disconnect previous TWS+ device");
+ disconnectExisting = true;
+ return false;
+ }
+ } else if (tws_connected && !mAdapterService.isTwsPlusDevice(device)) {
+ Log.d(TAG,"isConnectionAllowed: Disconnect tws device to connect to legacy headset");
+ disconnectExisting = true;
+ return false;
+ }
+ return false;
+ }
/**
* Check whether can connect to a peer device.
* The check considers the maximum number of connected peers.
@@ -312,8 +485,9 @@
*/
private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) {
int connected = 0;
+ boolean tws_device = false;
// Count devices that are in the process of connecting or already connected
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
for (A2dpStateMachine sm : mStateMachines.values()) {
switch (sm.getConnectionState()) {
case BluetoothProfile.STATE_CONNECTING:
@@ -321,6 +495,9 @@
if (Objects.equals(device, sm.getDevice())) {
return true; // Already connected or accounted for
}
+ if (tws_device == false) {
+ tws_device = mAdapterService.isTwsPlusDevice(sm.getDevice());
+ }
connected++;
break;
default:
@@ -328,6 +505,18 @@
}
}
}
+ Log.d(TAG,"connectionAllowedCheckMaxDevices connected = " + connected);
+ if (mAdapterService.isVendorIntfEnabled() &&
+ (tws_device || mAdapterService.isTwsPlusDevice(device) ||
+ (tws_device && connected == mMaxConnectedAudioDevices &&
+ !mAdapterService.isTwsPlusDevice(device)))) {
+ return isConnectionAllowed(device, tws_device, connected);
+ }
+ if (mSetMaxConnectedAudioDevices == 1 &&
+ connected == mSetMaxConnectedAudioDevices) {
+ disconnectExisting = true;
+ return true;
+ }
return (connected < mMaxConnectedAudioDevices);
}
@@ -354,6 +543,7 @@
+ " : too many connected devices");
return false;
}
+
// Check priority and accept or reject the connection.
// Note: Logic can be simplified, but keeping it this way for readability
int priority = getPriority(device);
@@ -382,7 +572,7 @@
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> devices = new ArrayList<>();
Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
for (BluetoothDevice device : bondedDevices) {
if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
BluetoothUuid.AudioSink)) {
@@ -411,7 +601,7 @@
@VisibleForTesting
List<BluetoothDevice> getDevices() {
List<BluetoothDevice> devices = new ArrayList<>();
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
for (A2dpStateMachine sm : mStateMachines.values()) {
devices.add(sm.getDevice());
}
@@ -421,7 +611,7 @@
public int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
return BluetoothProfile.STATE_DISCONNECTED;
@@ -432,7 +622,7 @@
private void removeActiveDevice(boolean forceStopPlayingAudio) {
BluetoothDevice previousActiveDevice = mActiveDevice;
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
// Clear the active device
mActiveDevice = null;
// This needs to happen before we inform the audio manager that the device
@@ -452,9 +642,28 @@
&& (getConnectionState(previousActiveDevice)
== BluetoothProfile.STATE_CONNECTED);
Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+
+ boolean isBAActive = false;
+ BATService mBatService = BATService.getBATService();
+ isBAActive = (mBatService != null) && (mBatService.isBATActive());
+ Log.d(TAG," removeActiveDevice: BA active " + isBAActive);
+ // If BA streaming is ongoing, we don't want to pause music player
+ if(isBAActive) {
+ suppressNoisyIntent = true;
+ Log.d(TAG," BA Active, suppress noisy intent");
+ }
+ if (mAdapterService.isTwsPlusDevice(previousActiveDevice) &&
+ mDummyDevice != null) {
+ previousActiveDevice = mDummyDevice;
+ mDummyDevice = null;
+ }
+ mDisconnectDelay =
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.A2DP, suppressNoisyIntent, -1);
+ if (mDisconnectDelay > 0) {
+ mDisconnectTime = SystemClock.uptimeMillis();
+ }
// Make sure the Active device in native layer is set to null and audio is off
if (!mA2dpNativeInterface.setActiveDevice(null)) {
Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
@@ -471,24 +680,42 @@
*/
public boolean setActiveDevice(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- synchronized (mStateMachines) {
- BluetoothDevice previousActiveDevice = mActiveDevice;
- if (DBG) {
- Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
- }
+ synchronized (mActiveDeviceLock) {
+ return setActiveDeviceInternal(device);
+ }
+ }
+ private boolean setActiveDeviceInternal(BluetoothDevice device) {
+ boolean deviceChanged;
+ BluetoothCodecStatus codecStatus = null;
+ BluetoothDevice previousActiveDevice = mActiveDevice;
+ boolean isBAActive = false;
+ Log.w(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
- if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
- }
+ if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
+ AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
+ } else if (previousActiveDevice != null && mAvrcp_ext != null) {
+ //Store volume only if SHO is triggered or output device other than BT is selected
+ mAvrcp_ext.storeVolumeForDevice(previousActiveDevice);
+ }
+ synchronized (mBtA2dpLock) {
+ BATService mBatService = BATService.getBATService();
+ isBAActive = (mBatService != null) && (mBatService.isBATActive());
+ Log.d(TAG," setActiveDevice: BA active " + isBAActive);
if (device == null) {
// Remove active device and continue playing audio only if necessary.
removeActiveDevice(false);
+ if(mAvrcp_ext != null)
+ mAvrcp_ext.setActiveDevice(device);
return true;
}
- BluetoothCodecStatus codecStatus = null;
A2dpStateMachine sm = mStateMachines.get(device);
+ deviceChanged = !Objects.equals(device, mActiveDevice);
+ if (!deviceChanged) {
+ Log.e(TAG, "setActiveDevice(" + device + "): already set to active ");
+ return true;
+ }
if (sm == null) {
Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
+ "no state machine");
@@ -499,58 +726,114 @@
+ "device is not connected");
return false;
}
+ if (mActiveDevice != null && mAdapterService.isTwsPlusDevice(device) &&
+ mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+ !Objects.equals(device, mActiveDevice) &&
+ getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
+ Log.d(TAG,"Ignore setActiveDevice request");
+ return false;
+ }
if (!mA2dpNativeInterface.setActiveDevice(device)) {
Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
return false;
}
codecStatus = sm.getCodecStatus();
-
- boolean deviceChanged = !Objects.equals(device, mActiveDevice);
mActiveDevice = device;
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in broadcastActiveDevice() for why.
broadcastActiveDevice(mActiveDevice);
- if (deviceChanged) {
- // Send an intent with the active device codec config
- if (codecStatus != null) {
- broadcastCodecConfig(mActiveDevice, codecStatus);
+ Log.w(TAG, "setActiveDevice coming out of mutex lock");
+ }
+ if (deviceChanged &&
+ (mDummyDevice == null || !mAdapterService.isTwsPlusDevice(mActiveDevice))) {
+ if(mAvrcp_ext != null)
+ mAvrcp_ext.setActiveDevice(device);
+ if (mAdapterService.isTwsPlusDevice(device) && mDummyDevice == null) {
+ Log.d(TAG,"set dummy device for tws+");
+ mDummyDevice = mAdapter.getRemoteDevice("FA:CE:FA:CE:FA:CE");
+ }
+ // Send an intent with the active device codec config
+ if (codecStatus != null) {
+ broadcastCodecConfig(mActiveDevice, codecStatus);
+ }
+ int rememberedVolume = -1;
+ if (AvrcpTargetService.get() != null) {
+ AvrcpTargetService.get().volumeDeviceSwitched(device);
+
+ rememberedVolume = AvrcpTargetService.get()
+ .getRememberedVolumeForDevice(device);
+ } else if (mAdapterService.isVendorIntfEnabled()) {
+ rememberedVolume = mAvrcp_ext.getVolume(device);
+ Log.d(TAG,"volume = " + rememberedVolume);
+ }
+ // Make sure the Audio Manager knows the previous Active device is disconnected,
+ // and the new Active device is connected.
+ // Also, mute and unmute the output during the switch to avoid audio glitches.
+ boolean wasMuted = false;
+ if (previousActiveDevice != null) {
+ if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
+ mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_MUTE,
+ mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ wasMuted = true;
}
- // Make sure the Audio Manager knows the previous Active device is disconnected,
- // and the new Active device is connected.
- // Also, mute and unmute the output during the switch to avoid audio glitches.
- boolean wasMuted = false;
- if (previousActiveDevice != null) {
- if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
- mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_MUTE, 0);
- wasMuted = true;
- }
+ if (mDummyDevice != null &&
+ mAdapterService.isTwsPlusDevice(previousActiveDevice)) {
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ mDummyDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.A2DP, true, -1);
+ mDummyDevice = null;
+ } else {
mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.A2DP, true, -1);
}
-
- int rememberedVolume = -1;
- if (AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
-
- rememberedVolume = AvrcpTargetService.get()
- .getRememberedVolumeForDevice(mActiveDevice);
+ }
+ // Check if ther is any delay set on audioservice for previous
+ // disconnect, if so then need to serialise disconnect/connect
+ // requests to audioservice, wait till prev disconnect is completed
+ if (mDisconnectDelay > 0) {
+ long currentTime = SystemClock.uptimeMillis();
+ if (mDisconnectDelay > (currentTime - mDisconnectTime)) {
+ try {
+ Log.d(TAG, "Enter wait for previous disconnect");
+ Thread.sleep(mDisconnectDelay - (currentTime - mDisconnectTime));
+ Log.d(TAG, "Exiting Wait for previous disconnect");
+ } catch (InterruptedException e) {
+ Log.e(TAG, "setactive was interrupted");
+ }
}
+ mDisconnectDelay = 0;
+ mDisconnectTime = 0;
+ }
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
- true, rememberedVolume);
-
- // Inform the Audio Service about the codec configuration
- // change, so the Audio Service can reset accordingly the audio
- // feeding parameters in the Audio HAL to the Bluetooth stack.
- mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
- if (wasMuted) {
- mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_UNMUTE, 0);
+ if (!isBAActive) {
+ if (mDummyDevice == null) {
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+ true, rememberedVolume);
+ } else {
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ mDummyDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+ true, rememberedVolume);
}
}
+
+ // Inform the Audio Service about the codec configuration
+ // change, so the Audio Service can reset accordingly the audio
+ // feeding parameters in the Audio HAL to the Bluetooth stack.
+ String offloadSupported =
+ SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+ if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
+ mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
+ }
+ if (wasMuted) {
+ mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_UNMUTE,
+ mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ }
+ if(mAvrcp_ext != null)
+ mAvrcp_ext.setAbsVolumeFlag(device);
}
return true;
}
@@ -562,13 +845,13 @@
*/
public BluetoothDevice getActiveDevice() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
return mActiveDevice;
}
}
private boolean isActiveDevice(BluetoothDevice device) {
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
return (device != null) && Objects.equals(device, mActiveDevice);
}
}
@@ -593,7 +876,10 @@
/* Absolute volume implementation */
public boolean isAvrcpAbsoluteVolumeSupported() {
- return mAvrcp.isAbsoluteVolumeSupported();
+ synchronized(mBtAvrcpLock) {
+ if (mAvrcp_ext != null) return mAvrcp_ext.isAbsoluteVolumeSupported();
+ return (mAvrcp != null) && mAvrcp.isAbsoluteVolumeSupported();
+ }
}
public void setAvrcpAbsoluteVolume(int volume) {
@@ -604,25 +890,53 @@
return;
}
- mAvrcp.setAbsoluteVolume(volume);
- }
-
- public void setAvrcpAudioState(int state) {
- mAvrcp.setA2dpAudioState(state);
- }
-
- public void resetAvrcpBlacklist(BluetoothDevice device) {
- if (mAvrcp != null) {
- mAvrcp.resetBlackList(device.getAddress());
+ synchronized(mBtAvrcpLock) {
+ if (mAvrcp_ext != null) {
+ mAvrcp_ext.setAbsoluteVolume(volume);
+ return;
+ }
+ if (mAvrcp != null) {
+ mAvrcp.setAbsoluteVolume(volume);
+ }
}
}
- boolean isA2dpPlaying(BluetoothDevice device) {
+ public void setAvrcpAudioState(int state, BluetoothDevice device) {
+ synchronized(mBtAvrcpLock) {
+ if (mAvrcp_ext != null) {
+ mAvrcp_ext.setA2dpAudioState(state, device);
+ } else if (mAvrcp != null) {
+ mAvrcp.setA2dpAudioState(state);
+ }
+ }
+
+ if(state == BluetoothA2dp.STATE_NOT_PLAYING) {
+ GattService mGattService = GattService.getGattService();
+ if(mGattService != null) {
+ Log.d(TAG, "Enable BLE scanning");
+ mGattService.setAptXLowLatencyMode(false);
+ }
+ }
+ }
+
+ public void resetAvrcpBlacklist(BluetoothDevice device) {
+ synchronized(mBtAvrcpLock) {
+ if (mAvrcp_ext != null) {
+ mAvrcp_ext.resetBlackList(device.getAddress());
+ return;
+ }
+ if (mAvrcp != null) {
+ mAvrcp.resetBlackList(device.getAddress());
+ }
+ }
+ }
+
+ public boolean isA2dpPlaying(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (DBG) {
Log.d(TAG, "isA2dpPlaying(" + device + ")");
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
return false;
@@ -644,7 +958,7 @@
if (DBG) {
Log.d(TAG, "getCodecStatus(" + device + ")");
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
if (device == null) {
device = mActiveDevice;
}
@@ -681,6 +995,21 @@
Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
return;
}
+
+ if((codecConfig.getCodecSpecific4() & AptxBLEScanMask) > 0) {
+ GattService mGattService = GattService.getGattService();
+
+ if(mGattService != null) {
+ long mScanMode = codecConfig.getCodecSpecific4() & AptxBLEScanMask;
+ if(mScanMode == Aptx_BLEScanEnable) {
+ mGattService.setAptXLowLatencyMode(false);
+ }
+ else if(mScanMode == Aptx_BLEScanDisable) {
+ Log.w(TAG, "Disable BLE scanning to support aptX LL Mode");
+ mGattService.setAptXLowLatencyMode(true);
+ }
+ }
+ }
mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
}
@@ -769,7 +1098,7 @@
void messageFromNative(A2dpStackEvent stackEvent) {
Objects.requireNonNull(stackEvent.device,
"Device should never be null, event: " + stackEvent);
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
BluetoothDevice device = stackEvent.device;
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
@@ -778,6 +1107,12 @@
case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
// Create a new state machine only when connecting to a device
+ if (mAdapterService.isVendorIntfEnabled())
+ mA2dpStackEvent = stackEvent.valueInt;
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ sm = getOrCreateStateMachine(device);
+ break;
+ }
if (!connectionAllowedCheckMaxDevices(device)) {
Log.e(TAG, "Cannot connect to " + device
+ " : too many connected devices");
@@ -786,14 +1121,40 @@
sm = getOrCreateStateMachine(device);
break;
default:
+ if (mAdapterService.isVendorIntfEnabled() &&
+ mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED ||
+ mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING) {
+ Log.d(TAG,"Reset local stack event value");
+ mA2dpStackEvent = EVENT_TYPE_NONE;
+ }
break;
}
}
+ } else {
+ if (mAdapterService.isVendorIntfEnabled()) {
+ switch (sm.getConnectionState()) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ mA2dpStackEvent = stackEvent.valueInt;
+ break;
+ default:
+ mA2dpStackEvent = EVENT_TYPE_NONE;
+ break;
+ }
+ }
}
if (sm == null) {
Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
return;
}
+ if (mA2dpSrcSnkConcurrency &&
+ A2dpStackEvent.CONNECTION_STATE_CONNECTING == stackEvent.valueInt) {
+ sA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ List<BluetoothDevice> srcDevs = sA2dpSinkService.getConnectedDevices();
+ for ( BluetoothDevice src : srcDevs ) {
+ Log.d(TAG, "calling sink disconnect to " + src);
+ sA2dpSinkService.disconnect(src);
+ }
+ }
sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
}
}
@@ -808,6 +1169,8 @@
*/
void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
boolean sameAudioFeedingParameters) {
+ Log.w(TAG, "codecConfigUpdated for device:" + device +
+ "sameAudioFeedingParameters: " + sameAudioFeedingParameters);
broadcastCodecConfig(device, codecStatus);
// Inform the Audio Service about the codec configuration change,
@@ -817,13 +1180,59 @@
mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
}
}
+ void updateTwsChannelMode(int state, BluetoothDevice device) {
+ if (mIsTwsPlusMonoSupported) {
+ BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+ Log.d(TAG, "TwsChannelMode: " + mTwsPlusChannelMode);
+ if ((state == BluetoothA2dp.STATE_PLAYING) && ("mono".equals(mTwsPlusChannelMode))) {
+ if ((peerTwsDevice!= null) && peerTwsDevice.isConnected() && isA2dpPlaying(peerTwsDevice)) {
+ Log.d(TAG, "setparameters to Dual-Mono");
+ mAudioManager.setParameters("TwsChannelConfig=dual-mono");
+ mTwsPlusChannelMode = "dual-mono";
+ }
+ } else if ("dual-mono".equals(mTwsPlusChannelMode)) {
+ if ((state == BluetoothA2dp.STATE_PLAYING) && (getConnectionState(peerTwsDevice) != BluetoothProfile.STATE_CONNECTED)) {
+ Log.d(TAG, "updateTwsChannelMode: send delay message ");
+ Message msg = mHandler.obtainMessage(SET_EBMONO_CFG);
+ mHandler.sendMessageDelayed(msg, MonoCfg_Timeout);
+ }
+ if ((state == BluetoothA2dp.STATE_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
+ if (mHandler.hasMessages(SET_EBMONO_CFG)) {
+ Log.d(TAG, "updateTwsChannelMode: remove delay message ");
+ mHandler.removeMessages(SET_EBMONO_CFG);
+ }
+ }
+ if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
+ Log.d(TAG, "setparameters to Mono");
+ mAudioManager.setParameters("TwsChannelConfig=mono");
+ mTwsPlusChannelMode = "mono";
+ }
+ }
+ } else {
+ Log.d(TAG,"TWS+ L/R to M feature not supported");
+ }
+ }
+
+ public void broadcastReconfigureA2dp() {
+ Log.w(TAG, "broadcastReconfigureA2dp(): set rcfg true to AudioManager");
+ boolean isBAActive = false;
+ BATService mBatService = BATService.getBATService();
+ isBAActive = (mBatService != null) && (mBatService.isBATActive());
+ Log.d(TAG," broadcastReconfigureA2dp: BA active " + isBAActive);
+ // If BA is active, don't inform AudioManager about reconfig.
+ if(isBAActive) {
+ return;
+ }
+ mAudioManager.setParameters("reconfigA2dp=true");
+ }
+
private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
return null;
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm != null) {
return sm;
@@ -900,7 +1309,7 @@
if (bondState != BluetoothDevice.BOND_NONE) {
return;
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
return;
@@ -913,7 +1322,7 @@
}
private void removeStateMachine(BluetoothDevice device) {
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
Log.w(TAG, "removeStateMachine: device " + device
@@ -927,11 +1336,11 @@
}
}
- private void updateOptionalCodecsSupport(BluetoothDevice device) {
+ public void updateOptionalCodecsSupport(BluetoothDevice device) {
int previousSupport = getSupportsOptionalCodecs(device);
boolean supportsOptional = false;
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
if (sm == null) {
return;
@@ -965,21 +1374,12 @@
if ((device == null) || (fromState == toState)) {
return;
}
- synchronized (mStateMachines) {
+ synchronized (mBtA2dpLock) {
if (toState == BluetoothProfile.STATE_CONNECTED) {
// Each time a device connects, we want to re-check if it supports optional
// codecs (perhaps it's had a firmware update, etc.) and save that state if
// it differs from what we had saved before.
updateOptionalCodecsSupport(device);
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
- }
- // Set the active device if only one connected device is supported and it was connected
- if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
- setActiveDevice(device);
- }
- // Check if the active device is not connected anymore
- if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
- setActiveDevice(null);
}
// Check if the device is disconnected - if unbond, remove the state machine
if (toState == BluetoothProfile.STATE_DISCONNECTED) {
@@ -1006,6 +1406,10 @@
return;
}
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device.getAddress().equals(BATService.mBAAddress)) {
+ Log.d(TAG," ConnectionUpdate from BA, don't take action ");
+ return;
+ }
int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
connectionStateChanged(device, fromState, toState);
@@ -1215,11 +1619,19 @@
public void dump(StringBuilder sb) {
super.dump(sb);
ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
- for (A2dpStateMachine sm : mStateMachines.values()) {
- sm.dump(sb);
+ synchronized(mBtA2dpLock) {
+ for (A2dpStateMachine sm : mStateMachines.values()) {
+ sm.dump(sb);
+ }
}
- if (mAvrcp != null) {
- mAvrcp.dump(sb);
+ synchronized(mBtAvrcpLock) {
+ if (mAvrcp_ext != null) {
+ mAvrcp_ext.dump(sb);
+ return;
+ }
+ if (mAvrcp != null) {
+ mAvrcp.dump(sb);
+ }
}
}
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 3ab26e1..fdc9a33 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -64,6 +64,8 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Scanner;
+import android.os.SystemProperties;
+import com.android.bluetooth.btservice.AdapterService;
final class A2dpStateMachine extends StateMachine {
private static final boolean DBG = true;
@@ -130,7 +132,7 @@
// Stop if auido is still playing
log("doQuit: stopped playing " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+ mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@@ -148,8 +150,9 @@
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
- mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
-
+ synchronized (this) {
+ mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+ }
removeDeferredMessages(DISCONNECT);
if (mLastConnectionState != -1) {
@@ -158,10 +161,15 @@
if (mIsPlaying) {
Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+ mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService.isVendorIntfEnabled() &&
+ adapterService.isTwsPlusDevice(mDevice)) {
+ mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
+ }
}
}
@@ -264,7 +272,9 @@
Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
- mConnectionState = BluetoothProfile.STATE_CONNECTING;
+ synchronized (this) {
+ mConnectionState = BluetoothProfile.STATE_CONNECTING;
+ }
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
@@ -360,7 +370,9 @@
Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
- mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+ synchronized (this) {
+ mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+ }
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
@@ -464,8 +476,9 @@
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
- mConnectionState = BluetoothProfile.STATE_CONNECTED;
-
+ synchronized (this) {
+ mConnectionState = BluetoothProfile.STATE_CONNECTED;
+ }
removeDeferredMessages(CONNECT);
broadcastConnectionState(mConnectionState, mLastConnectionState);
@@ -559,9 +572,15 @@
if (!mIsPlaying) {
Log.i(TAG, "Connected: started playing: " + mDevice);
mIsPlaying = true;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
+ mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING, mDevice);
broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_NOT_PLAYING);
+ Log.i(TAG,"state:AUDIO_STATE_STARTED");
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService.isVendorIntfEnabled() &&
+ adapterService.isTwsPlusDevice(mDevice)) {
+ mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice);
+ }
}
}
break;
@@ -571,7 +590,7 @@
if (mIsPlaying) {
Log.i(TAG, "Connected: stopped playing: " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
+ mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@@ -594,7 +613,7 @@
boolean isConnected() {
synchronized (this) {
- return (getCurrentState() == mConnected);
+ return (mConnectionState == BluetoothProfile.STATE_CONNECTED);
}
}
@@ -613,13 +632,32 @@
// NOTE: This event is processed in any state
private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
BluetoothCodecConfig prevCodecConfig = null;
+ BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+ int new_codec_type = newCodecStatus.getCodecConfig().getCodecType();
+ String offloadSupported =
+ SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+ if (DBG) Log.d(TAG, "START of A2dpService");
+ Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type);
+ // Split A2dp will be enabled by default
+ if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
+ if (new_codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService.isVendorIntfEnabled() &&
+ adapterService.isTwsPlusDevice(mDevice)) {
+ Log.d(TAG,"TWSP device streaming,not calling reconfig");
+ return;
+ }
+ mA2dpService.broadcastReconfigureA2dp();
+ Log.w(TAG,"Split A2dp enabled rcfg send to Audio for codec max");
+ return;
+ }
+ }
synchronized (this) {
if (mCodecStatus != null) {
prevCodecConfig = mCodecStatus.getCodecConfig();
}
mCodecStatus = newCodecStatus;
}
-
if (DBG) {
Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
+ newCodecStatus.getCodecConfig());
@@ -633,6 +671,14 @@
}
}
+ if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
+ // Remote selectable codec could be changed if codec config changed
+ // in connected state, we need to re-check optional codec status
+ // for this codec change event.
+ Log.d(TAG,"updating optional codec support as previous and current codec are different.");
+ mA2dpService.updateOptionalCodecsSupport(mDevice);
+ }
+
if (mA2dpOffloadEnabled) {
boolean update = false;
BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -654,9 +700,28 @@
return;
}
- boolean sameAudioFeedingParameters =
- newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
- mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
+ if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
+ boolean isUpdateRequired = false;
+ if ((prevCodecConfig != null) && (prevCodecConfig.getCodecType() != new_codec_type)) {
+ Log.d(TAG, "previous codec is differs from new codec");
+ isUpdateRequired = true;
+ } else if (!newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig)) {
+ Log.d(TAG, "codec config parameters mismatched with previous config: ");
+ isUpdateRequired = true;
+ } else if ((newCodecStatus.getCodecConfig().getCodecType()
+ == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
+ && (prevCodecConfig != null)
+ && (prevCodecConfig.getCodecSpecific1()
+ != newCodecStatus.getCodecConfig().getCodecSpecific1())) {
+ Log.d(TAG, "LDAC: codec config parameters mismatched with previous config: ");
+ isUpdateRequired = true;
+ }
+ Log.d(TAG, "isUpdateRequired: " + isUpdateRequired);
+ //update MM only when previous and current codec config has been changed.
+ if (isUpdateRequired) {
+ mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
+ }
+ }
}
// This method does not check for error conditon (newState == prevState)
@@ -715,6 +780,16 @@
return Integer.toString(what);
}
+ private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+ BluetoothCodecStatus newCodecStatus) {
+ if (prevCodecStatus == null) {
+ return false;
+ }
+ return BluetoothCodecStatus.sameCapabilities(
+ prevCodecStatus.getCodecsSelectableCapabilities(),
+ newCodecStatus.getCodecsSelectableCapabilities());
+ }
+
private static String profileStateToString(int state) {
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 17c8885..a27f187 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -19,18 +19,34 @@
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothA2dpSink;
+import android.content.Context;
import android.content.Intent;
+import android.media.AudioFormat;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
+import android.widget.Toast;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.btservice.ProfileService;
import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
* Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
@@ -40,8 +56,26 @@
private static final boolean DBG = true;
private static final String TAG = "A2dpSinkService";
+ /* HashMap of A2dpSinkStateMachines for remote connected devices*/
+ private final ConcurrentMap<BluetoothDevice, A2dpSinkStateMachine> mStateMachines =
+ new ConcurrentHashMap<>();
+ private HandlerThread mStateMachinesThread;
+ private final Object mBtA2dpLock = new Object();
+ private AdapterService mAdapterService;
+
private A2dpSinkStateMachine mStateMachine;
private static A2dpSinkService sA2dpSinkService;
+ private static A2dpService sA2dpService;
+ private static HeadsetService sHeadsetService;
+ private static boolean mA2dpSrcSnkConcurrency;
+ protected static BluetoothDevice mStreamingDevice;
+
+ private static int mMaxA2dpSinkConnections = 1;
+ public static final int MAX_ALLOWED_SINK_CONNECTIONS = 2;
+
+ static {
+ classInitNative();
+ }
@Override
protected IProfileServiceBinder initBinder() {
@@ -53,10 +87,26 @@
if (DBG) {
Log.d(TAG, "start()");
}
+
+ initNative();
+ mStateMachines.clear();
+ mStateMachinesThread = new HandlerThread("A2dpSinkService.StateMachines");
+ mStateMachinesThread.start();
+
+ mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+ "AdapterService cannot be null when A2dpService starts");
+
+ mMaxA2dpSinkConnections = Math.min(
+ SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
+ MAX_ALLOWED_SINK_CONNECTIONS);
+ mA2dpSrcSnkConcurrency= SystemProperties.getBoolean(
+ "persist.vendor.service.bt.a2dp_concurrency", false);
+ if (DBG) {
+ Log.d(TAG, "A2DP concurrency set to " + mA2dpSrcSnkConcurrency);
+ }
// Start the media browser service.
Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
startService(startIntent);
- mStateMachine = A2dpSinkStateMachine.make(this, this);
setA2dpSinkService(this);
return true;
}
@@ -67,9 +117,18 @@
Log.d(TAG, "stop()");
}
setA2dpSinkService(null);
- if (mStateMachine != null) {
- mStateMachine.doQuit();
+
+ // Step 4: Destroy state machines and stop handler thread
+ synchronized (mBtA2dpLock) {
+ for (A2dpSinkStateMachine sm : mStateMachines.values()) {
+ sm.doQuit();
+ sm.cleanup();
+ }
+ mStateMachines.clear();
}
+ mStateMachinesThread.quitSafely();
+ mStateMachinesThread = null;
+
Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
stopService(stopIntent);
return true;
@@ -77,9 +136,21 @@
@Override
protected void cleanup() {
- if (mStateMachine != null) {
- mStateMachine.cleanup();
- }
+ cleanupNative();
+ }
+
+ protected void removeStateMachine(BluetoothDevice device) {
+ synchronized (mBtA2dpLock) {
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ Log.e(TAG, "State Machine not found for device:" + device);
+ return;
+ }
+ mStateMachines.remove(device);
+ sm.doQuit();
+ sm.cleanup();
+ sm = null;
+ }
}
//API Methods
@@ -106,45 +177,110 @@
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 (DBG) Log.d(TAG, "connect(): " + device);
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) {
+ if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+ BluetoothUuid.AudioSource)) {
+ Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Source UUID");
return false;
}
- mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
+ A2dpSinkStateMachine sm = null;
+ synchronized (mBtA2dpLock) {
+ sm = getOrCreateStateMachine(device);
+ if (sm != null) {
+ int connectionState = sm.getConnectionState();
+ if (connectionState == BluetoothProfile.STATE_CONNECTED
+ || connectionState == BluetoothProfile.STATE_CONNECTING) {
+ Log.e(TAG, "Device (" + device + ") is already connected/connecting. Ignore");
+ return false;
+ }
+ } else if (sm == null) {
+ return false;
+ }
+
+ if (mA2dpSrcSnkConcurrency) {
+ sA2dpService = A2dpService.getA2dpService();
+ List<BluetoothDevice> snkDevs = sA2dpService.getConnectedDevices();
+ for( BluetoothDevice snk : snkDevs ) {
+ Log.d(TAG, "calling src disconnect to " + snk);
+ sA2dpService.disconnect(snk);
+ }
+ sHeadsetService = HeadsetService.getHeadsetService();
+ List<BluetoothDevice> hsDevs = sHeadsetService.getConnectedDevices();
+ for ( BluetoothDevice hs : hsDevs ) {
+ Log.d(TAG, "calling headset disconnect to " + hs);
+ sHeadsetService.disconnect(hs);
+ }
+ }
+
+ sm.sendMessage(A2dpSinkStateMachine.CONNECT);
+ }
return true;
}
- public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectedDevices();
+ public boolean disconnect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+ if (DBG) {
+ Log.d(TAG, "disconnect(): " + device);
+ }
+
+ synchronized (mBtA2dpLock) {
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
+ return false;
+ }
+ // State check before Disconnect
+ int connectionState = sm.getConnectionState();
+ if (connectionState != BluetoothProfile.STATE_CONNECTED
+ && connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ sm.sendMessage(A2dpSinkStateMachine.DISCONNECT);
+ return true;
+ }
}
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getDevicesMatchingConnectionStates(states);
+ List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+ synchronized (mStateMachines) {
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+
+ for (BluetoothDevice device : bondedDevices) {
+ ParcelUuid[] featureUuids = device.getUuids();
+ if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
+ continue;
+ }
+ connectionState = BluetoothProfile.STATE_DISCONNECTED;
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm != null) {
+ connectionState = sm.getConnectionState();
+ }
+ for (int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ }
+ return deviceList;
}
- int getConnectionState(BluetoothDevice device) {
+ public int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectionState(device);
+ synchronized (mBtA2dpLock) {
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm != null) {
+ return sm.getConnectionState();
+ }
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
}
public boolean setPriority(BluetoothDevice device, int priority) {
@@ -174,6 +310,14 @@
* 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) {
+ A2dpSinkStateMachine mStateMachine = null;
+ synchronized (mBtA2dpLock) {
+ mStateMachine = mStateMachines.get(device);
+ if (mStateMachine == null) {
+ Log.w(TAG, "state machine is not present for device:" + device);
+ return;
+ }
+ }
if (mStateMachine != null) {
if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY
&& keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
@@ -194,10 +338,25 @@
* stopping playback.
*/
public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
+ Log.d(TAG, "informTGStatePlaying: device: " + device
+ + ", mStreamingDevice:" + mStreamingDevice);
+ A2dpSinkStateMachine mStateMachine = null;
+ synchronized (mBtA2dpLock) {
+ mStateMachine = mStateMachines.get(device);
+ if (mStateMachine == null) {
+ return;
+ }
+ }
if (mStateMachine != null) {
if (!isPlaying) {
mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
} else {
+ // Soft-Handoff from AVRCP Cmd (if received before AVDTP_START)
+ initiateHandoffOperations(device);
+ if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+ Log.d(TAG, "updating streaming device after avrcp status command");
+ mStreamingDevice = device;
+ }
mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
}
}
@@ -211,9 +370,14 @@
* 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);
+ A2dpSinkStateMachine sm = null;
+ synchronized (mBtA2dpLock) {
+ sm = mStateMachines.get(device);
+ if (sm == null) {
+ return;
+ }
}
+ sm.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
}
synchronized boolean isA2dpPlaying(BluetoothDevice device) {
@@ -221,12 +385,25 @@
if (DBG) {
Log.d(TAG, "isA2dpPlaying(" + device + ")");
}
+ synchronized (mBtA2dpLock) {
+ A2dpSinkStateMachine mStateMachine = mStateMachines.get(device);
+ if (mStateMachine == null) {
+ return false;
+ }
+ }
return mStateMachine.isPlaying(device);
}
BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getAudioConfig(device);
+ A2dpSinkStateMachine sm = null;
+ synchronized (mBtA2dpLock) {
+ sm = mStateMachines.get(device);
+ if (sm == null) {
+ return null;
+ }
+ }
+ return sm.getAudioConfig(device);
}
//Binder object: Must be static class or memory leak may occur
@@ -346,4 +523,194 @@
mStateMachine.dump(sb);
}
}
+
+ /* Get Number of connected/connecting devices*/
+ public List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ synchronized (mBtA2dpLock) {
+ List<BluetoothDevice> devices = new ArrayList<>();
+ for (A2dpSinkStateMachine sm : mStateMachines.values()) {
+ if (sm.isConnected()) {
+ devices.add(sm.getDevice());
+ }
+ }
+ return devices;
+ }
+ }
+
+ /* This API returns existing state machine for remote device or creates new if not present.*/
+ private A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ if (device == null) {
+ Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
+ return null;
+ }
+ synchronized (mBtA2dpLock) {
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm != null) {
+ Log.i(TAG, "Return existing state machine for device:" + device);
+ return sm;
+ }
+ // Limit the maximum number of state machines to avoid DoS
+ if (mStateMachines.size() >= mMaxA2dpSinkConnections) {
+ Log.e(TAG, "Maximum number of A2DP Sink Connections reached: "
+ + mMaxA2dpSinkConnections);
+ return null;
+ }
+ if (DBG) {
+ Log.d(TAG, "Creating a new state machine for " + device);
+ }
+
+ sm = A2dpSinkStateMachine.make(this, mStateMachinesThread.getLooper(), device);
+ mStateMachines.put(device, sm);
+ return sm;
+ }
+ }
+
+ public static BluetoothDevice getCurrentStreamingDevice() {
+ return mStreamingDevice;
+ }
+
+ /* This API performs all the operations required for doing soft-Handoff */
+ public synchronized void initiateHandoffOperations(BluetoothDevice device) {
+ if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+ Log.d(TAG, "Soft-Handoff. Prev Device:" + mStreamingDevice + ", New: " + device);
+
+ for (A2dpSinkStateMachine otherSm: mStateMachines.values()) {
+ BluetoothDevice otherDevice = otherSm.getDevice();
+ if (mStreamingDevice.equals(otherDevice)) {
+ Log.d(TAG, "Release Audio Focus for " + otherDevice);
+ otherSm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+ // Send Passthrough Command for PAUSE
+ AvrcpControllerService avrcpService =
+ AvrcpControllerService.getAvrcpControllerService();
+ avrcpService.sendPassThroughCmd(mStreamingDevice,
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_PRESSED);
+ avrcpService.sendPassThroughCmd(mStreamingDevice,
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_RELEASED);
+
+ // send intent for updated streaming device so that media session is updated
+ Intent intent = new Intent(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+ /* set autoconnect priority of non-streaming device to PRIORITY_ON and priority
+ * of streaming device to PRIORITY_AUTO_CONNECT */
+ setPriority(otherDevice, BluetoothProfile.PRIORITY_ON);
+ setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ break;
+ }
+ }
+ } else if (mStreamingDevice == null && device != null) {
+ Log.d(TAG, "Prev Device: Null. New Streaming Device: " + device);
+ // No Action Required
+ }
+ }
+
+ /* JNI Changes for SINK SHO */
+ private void onConnectionStateChanged(byte[] address, int state) {
+ BluetoothDevice device = getDevice(address);
+ Log.d(TAG, "onConnectionStateChanged. State = " + state + ", device:" + device
+ + ", streaming:" + mStreamingDevice);
+ A2dpSinkStateMachine sm = getOrCreateStateMachine(device);
+ if (sm == null || device == null) {
+ Log.e(TAG, "State Machine not found for device:" + device + ". Return.");
+ return;
+ }
+
+ // If streaming device is disconnected, release audio focus and update mStreamingDevice
+ if (state == BluetoothProfile.STATE_DISCONNECTED && device.equals(mStreamingDevice)) {
+ Log.d(TAG, "Release Audio Focus for Streaming device: " + device);
+ sm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+ mStreamingDevice = null;
+ }
+
+ // Intiate Handoff operations when state has been connectiond
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+ Log.d(TAG, "current connected device: " + device + "is different from previous device");
+ initiateHandoffOperations(device);
+ mStreamingDevice = device;
+ } else if (device != null) {
+ mStreamingDevice = device;
+ }
+ }
+
+ A2dpSinkStateMachine.StackEvent event =
+ sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.device = device;
+ event.valueInt = state;
+ if (mA2dpSrcSnkConcurrency &&
+ state == BluetoothProfile.STATE_CONNECTING) {
+ sA2dpService = A2dpService.getA2dpService();
+ List<BluetoothDevice> snkDevs = sA2dpService.getConnectedDevices();
+ for ( BluetoothDevice snk : snkDevs ) {
+ Log.d(TAG, "calling src disconnect to " + snk);
+ sA2dpService.disconnect(snk);
+ }
+ sHeadsetService = HeadsetService.getHeadsetService();
+ List<BluetoothDevice> hsDevs = sHeadsetService.getConnectedDevices();
+ for ( BluetoothDevice hs : hsDevs ) {
+ Log.d(TAG, "calling headset disconnect to " + hs);
+ sHeadsetService.disconnect(hs);
+ }
+ }
+ sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
+
+ private void onAudioStateChanged(byte[] address, int state) {
+ BluetoothDevice device = getDevice(address);
+ Log.d(TAG, "onAudioStateChanged. Audio State = " + state + ", device:" + device);
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ return;
+ }
+
+ // Intiate Handoff operations if AUDIO_STATE_STARTED for other connected device
+ if (state == A2dpSinkStateMachine.AUDIO_STATE_STARTED) {
+ initiateHandoffOperations(device);
+ mStreamingDevice = device; // mark playing device as streaming device
+ }
+
+ A2dpSinkStateMachine.StackEvent event =
+ sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.device = device;
+ event.valueInt = state;
+ sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
+
+ private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+ BluetoothDevice device = getDevice(address);
+ Log.d(TAG, "onAudioConfigChanged:- device:" + device + " samplerate:" + sampleRate
+ + ", channelCount:" + channelCount);
+ A2dpSinkStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ return;
+ }
+
+ A2dpSinkStateMachine.StackEvent event =
+ sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+ event.device = device;
+ int channelConfig =
+ (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
+ event.audioConfig =
+ new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
+ sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
+
+ private static native void classInitNative();
+
+ private native void initNative();
+
+ private native void cleanupNative();
+
+ public static native boolean connectA2dpNative(byte[] address);
+
+ public static native boolean disconnectA2dpNative(byte[] address);
+
+ public static native void informAudioFocusStateNative(int focusGranted);
+
+ public static native void informAudioTrackGainNative(float focusGranted);
+
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
old mode 100644
new mode 100755
index fb64318..fea7080
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -38,7 +38,9 @@
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioSystem;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
@@ -64,13 +66,14 @@
static final int CONNECT = 1;
static final int DISCONNECT = 2;
- private static final int STACK_EVENT = 101;
+ protected 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;
+ public static final int EVENT_RELEASE_FOCUS = 306;
private static final int IS_INVALID_DEVICE = 0;
private static final int IS_VALID_DEVICE = 1;
@@ -78,85 +81,71 @@
public static final int AVRC_ID_PAUSE = 0x46;
public static final int KEY_STATE_PRESSED = 0;
public static final int KEY_STATE_RELEASED = 1;
+ private static final String BT_ADDR_KEY = "bt_addr";
// 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 Connecting mConnecting;
+ private Disconnecting mDisconnecting;
private Connected mConnected;
private A2dpSinkService mService;
private Context mContext;
private BluetoothAdapter mAdapter;
- private IntentBroadcastHandler mIntentBroadcastHandler;
private static final int MSG_CONNECTION_STATE_CHANGED = 0;
private final Object mLockForPatch = new Object();
- // 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 BluetoothDevice mDevice;
+ private static final String TAG = "A2dpSinkStateMachine";
+ private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+ private boolean mIsPlaying = false;
+ private Looper mLooper;
private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
new HashMap<BluetoothDevice, BluetoothAudioConfig>();
+ private int mLastConnectionState = -1;
- static {
- classInitNative();
- }
-
- private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
- super("A2dpSinkStateMachine");
+ private A2dpSinkStateMachine(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
+ super(TAG, looper);
mService = svc;
- mContext = context;
+ mContext = svc;
+ mLooper = looper;
+ mDevice = device;
mAdapter = BluetoothAdapter.getDefaultAdapter();
- initNative();
+ if (Looper.myLooper() == null)
+ Looper.prepare();
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);
+
+ if (mAdapter != null) {
+ String bdAddr = mAdapter.getAddress();
+ AudioSystem.setParameters(BT_ADDR_KEY + "=" + bdAddr);
+ log("AudioSystem.setParameters, Key: " + BT_ADDR_KEY + " Value: " + bdAddr);
+ }
setInitialState(mDisconnected);
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
- mIntentBroadcastHandler = new IntentBroadcastHandler();
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
- static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
+ static A2dpSinkStateMachine make(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
Log.d("A2dpSinkStateMachine", "make");
- A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
+ A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, looper, device);
a2dpSm.start();
return a2dpSm;
}
@@ -165,6 +154,11 @@
if (DBG) {
Log.d("A2dpSinkStateMachine", "Quit");
}
+ if (mIsPlaying) {
+ mIsPlaying = false;
+ broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
+ }
synchronized (A2dpSinkStateMachine.this) {
mStreaming = null;
}
@@ -172,47 +166,51 @@
}
public void cleanup() {
- cleanupNative();
mAudioConfigs.clear();
}
public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
- ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
- ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
+ ProfileService.println(sb, "mDevice: " + mDevice);
ProfileService.println(sb, "StateMachine: " + this.toString());
+ ProfileService.println(sb, "isPlaying: " + mIsPlaying);
}
private class Disconnected extends State {
@Override
public void enter() {
- log("Enter Disconnected: " + getCurrentMessage().what);
+ log("Enter Disconnected (Device: " + mDevice + ") : " + getCurrentMessage().what);
+ mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+ if (mIsPlaying) {
+ Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
+ mIsPlaying = false;
+ broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
+ }
+ if (mLastConnectionState != -1) {
+ log("Quit State Machine for device:" + mDevice);
+ broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+ mService.removeStateMachine(mDevice);
+ }
}
@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;
- }
+ log("Disconnected (Device: " + mDevice + ") Process Message: " + message.what);
boolean retValue = HANDLED;
switch (message.what) {
case CONNECT:
- BluetoothDevice device = (BluetoothDevice) message.obj;
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTED);
+ if (mDevice == null) {
+ Log.e(TAG, "State Machine for Null Device Reference, Return.");
+ return NOT_HANDLED;
+ }
- if (!connectA2dpNative(getByteAddress(device))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
+ if (!A2dpSinkService.connectA2dpNative(getByteAddress(mDevice))) {
break;
}
synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = device;
- transitionTo(mPending);
+ transitionTo(mConnecting);
}
// TODO(BT) remove CONNECT_TIMEOUT when the stack
// sends back events consistently
@@ -243,44 +241,39 @@
@Override
public void exit() {
- log("Exit Disconnected: " + getCurrentMessage().what);
+ log("Exit Disconnected: (" + mDevice + "): " + getCurrentMessage().what);
+ mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
}
// in Disconnected state
private void processConnectionEvent(BluetoothDevice device, int state) {
switch (state) {
case CONNECTION_STATE_DISCONNECTED:
- logw("Ignore A2DP DISCONNECTED event, device: " + device);
+ logw("Device: " + device + " already in disconnected state. Ignore ");
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);
+ transitionTo(mConnecting);
}
} else {
//reject the connection and stay in Disconnected state itself
logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
+ A2dpSinkService.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;
transitionTo(mConnected);
}
} else {
//reject the connection and stay in Disconnected state itself
logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
+ A2dpSinkService.disconnectA2dpNative(getByteAddress(device));
}
break;
case CONNECTION_STATE_DISCONNECTING:
@@ -293,36 +286,41 @@
}
}
- private class Pending extends State {
+ private class Connecting extends State {
@Override
public void enter() {
- log("Enter Pending: " + getCurrentMessage().what);
+ log("Enter CONNECTING: " + getCurrentMessage().what);
+ mConnectionState = BluetoothProfile.STATE_CONNECTING;
+ broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+ }
+
+ @Override
+ public void exit() {
+ log("Exit CONNECTING: " + getCurrentMessage().what);
+ mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
}
@Override
public boolean processMessage(Message message) {
- log("Pending process message: " + message.what);
+ log("SM STATE: CONNECTING (" + mDevice + ") process message: " + message.what);
boolean retValue = HANDLED;
switch (message.what) {
case CONNECT:
- logd("Disconnect before connecting to another target");
+ logd("Connection is already in pregress.");
break;
case CONNECT_TIMEOUT:
- onConnectionStateChanged(getByteAddress(mTargetDevice),
+ onConnectionStateChanged(getByteAddress(mDevice),
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;
- }
+ if (mDevice == null) {
+ Log.e(TAG, "Message received for wrong device.");
+ return NOT_HANDLED;
}
+ // cancel connection to the mDevice
+ A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice)); //
+ transitionTo(mDisconnected);
break;
case STACK_EVENT:
StackEvent event = (StackEvent) message.obj;
@@ -349,134 +347,111 @@
// 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:
+ // connection failed
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);
+ synchronized (A2dpSinkStateMachine.this) {
+ transitionTo(mDisconnected);
}
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);
- }
+ // Connection completed
+ synchronized (A2dpSinkStateMachine.this) {
+ 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");
- }
+ log("current device tries to connect back. 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);
+ // remote trying to disconnect
+ if (DBG) {
+ log("stack is disconnecting mDevice");
+ }
+ transitionTo(mDisconnecting);
+ break;
+ default:
+ loge("Incorrect state: " + state);
+ break;
+ }
+ }
+ }
+
+ private class Disconnecting extends State {
+ @Override
+ public void enter() {
+ log("Enter Disconnecting: " + getCurrentMessage().what);
+ mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+ broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("DISCONNECTING (" + mDevice + ") process message: " + message.what);
+
+ boolean retValue = HANDLED;
+ switch (message.what) {
+ case CONNECT:
+ logd("Disconnection is in progress. Try again later.");
+ break;
+ case CONNECT_TIMEOUT:
+ onConnectionStateChanged(getByteAddress(mDevice),
+ CONNECTION_STATE_DISCONNECTED);
+ break;
+ case DISCONNECT:
+ Log.e(TAG, "Message received for wrong device.");
+ break;
+
+ case EVENT_RELEASE_FOCUS:
+ mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
+ 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;
+ }
+ return retValue;
+ }
+
+ // in Pending state
+ private void processConnectionEvent(BluetoothDevice device, int state) {
+ log("processConnectionEvent state " + state);
+ switch (state) {
+ case CONNECTION_STATE_DISCONNECTED:
+ mAudioConfigs.remove(device);
+ synchronized (A2dpSinkStateMachine.this) {
+ transitionTo(mDisconnected);
+ }
+ break;
+ case CONNECTION_STATE_CONNECTED:
+ // disconnection failed
+ synchronized (A2dpSinkStateMachine.this) {
+ transitionTo(mConnected);
+ }
+ break;
+ case CONNECTION_STATE_CONNECTING:
+ log("Current device tries to connect back.");
+ transitionTo(mConnecting);
+ break;
+ case CONNECTION_STATE_DISCONNECTING:
+ // we already broadcasted the intent, doing nothing here
+ if (DBG) {
+ log("stack is disconnecting mDevice. Ignore.");
}
break;
default:
@@ -485,56 +460,61 @@
}
}
+ @Override
+ public void exit() {
+ log("Exit Disconnecting: (" + mDevice + "): " + getCurrentMessage().what);
+ mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+ }
}
private class Connected extends State {
@Override
public void enter() {
- log("Enter Connected: " + getCurrentMessage().what);
+ log("Enter Connected (Device: "+ mDevice + ") " + getCurrentMessage().what);
// Upon connected, the audio starts out as stopped
- broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ mConnectionState = BluetoothProfile.STATE_CONNECTED;
+ broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
BluetoothA2dpSink.STATE_PLAYING);
+ broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
synchronized (A2dpSinkStateMachine.this) {
if (mStreaming == null) {
if (DBG) {
log("Creating New A2dpSinkStreamHandler");
}
- mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
+ mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this,
+ mContext, mDevice);
}
}
if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
- informAudioFocusStateNative(0);
+ A2dpSinkService.informAudioFocusStateNative(0);
}
}
@Override
+ public void exit() {
+ log("Exit Connected: (" + mDevice + "): " + getCurrentMessage().what);
+ mLastConnectionState = 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");
+ log("Connected (Device: "+ mDevice + ") process message: " + message.what);
+ if (mDevice == null) {
+ loge("ERROR: Current Device is null in Connected");
return NOT_HANDLED;
}
switch (message.what) {
case CONNECT:
- logd("Disconnect before connecting to another target");
+ logd("Connect received in Connected State");
break;
case DISCONNECT: {
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mCurrentDevice.equals(device)) {
+ if (!A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice))) {
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);
+ transitionTo(mDisconnecting);
}
break;
@@ -576,6 +556,10 @@
mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
break;
+ case EVENT_RELEASE_FOCUS:
+ mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
+ break;
+
default:
return NOT_HANDLED;
}
@@ -584,21 +568,18 @@
// in Connected state
private void processConnectionEvent(BluetoothDevice device, int state) {
+ if (mDevice != null && device != null && !mDevice.equals(device)) {
+ Log.e(TAG, "Message received for wrong device.");
+ return;
+ }
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);
+ if (mDevice.equals(device)) {
synchronized (A2dpSinkStateMachine.this) {
// Take care of existing audio focus in the streaming state machine.
mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
.sendToTarget();
- mCurrentDevice = null;
transitionTo(mDisconnected);
}
} else {
@@ -612,19 +593,29 @@
}
private void processAudioStateEvent(BluetoothDevice device, int state) {
- if (!mCurrentDevice.equals(device)) {
+ if (!mDevice.equals(device)) {
loge("Audio State Device:" + device + "is different from ConnectedDevice:"
- + mCurrentDevice);
+ + mDevice);
return;
}
log(" processAudioStateEvent in state " + state);
switch (state) {
case AUDIO_STATE_STARTED:
+ mIsPlaying = true;
mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+ 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();
+ mIsPlaying = false;
+ if (mDevice.equals(A2dpSinkService.mStreamingDevice)) {
+ mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+ } else {
+ Log.d(TAG, "other than streaming device. Ignore");
+ }
+ broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
break;
default:
loge("Audio State Device: " + device + " bad state: " + state);
@@ -639,36 +630,8 @@
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.equals(device)) {
- return BluetoothProfile.STATE_CONNECTED;
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- } else {
- loge("Bad currentState: " + currentState);
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- }
+ int getConnectionState() {
+ return mConnectionState;
}
BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
@@ -679,7 +642,7 @@
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
synchronized (this) {
if (getCurrentState() == mConnected) {
- devices.add(mCurrentDevice);
+ devices.add(mDevice);
}
}
return devices;
@@ -687,13 +650,19 @@
boolean isPlaying(BluetoothDevice device) {
synchronized (this) {
- if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
- return true;
+ if ((mDevice != null) && (device.equals(mDevice))) {
+ return mIsPlaying;
}
}
return false;
}
+ boolean isConnected() {
+ synchronized (this) {
+ return (getCurrentState() == mConnected);
+ }
+ }
+
// Utility Functions
boolean okToConnect(BluetoothDevice device) {
AdapterService adapterService = AdapterService.getAdapterService();
@@ -711,34 +680,17 @@
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);
+ if (prevState != newState && newState == 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, newState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
}
private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
@@ -794,43 +746,17 @@
return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
}
- private class StackEvent {
+ public class StackEvent {
public int type = EVENT_TYPE_NONE;
public BluetoothDevice device = null;
public int valueInt = 0;
public BluetoothAudioConfig audioConfig = null;
- private StackEvent(int type) {
+ public 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 =
@@ -851,11 +777,15 @@
}
}
+ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
// 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 static final int EVENT_TYPE_NONE = 0;
+ protected static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ protected static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+ protected static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
// Do not modify without updating the HAL bt_av.h files.
@@ -870,17 +800,4 @@
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);
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index f442c49..5b96e13 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -29,6 +29,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.CoverArtUtils;
import java.util.List;
@@ -51,7 +52,7 @@
* restored.
*/
public class A2dpSinkStreamHandler extends Handler {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "A2dpSinkStreamHandler";
// Configuration Variables
@@ -69,6 +70,7 @@
public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
public static final int DELAYED_RESUME = 9; // If a call just ended allow stack time to settle
+ public static final int RELEASE_FOCUS = 10; // Release focus when requested
// Used to indicate focus lost
private static final int STATE_FOCUS_LOST = 0;
@@ -84,7 +86,7 @@
private boolean mSentPause = false;
// Keep track of the relevant audio focus (None, Transient, Gain)
private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
-
+ private BluetoothDevice mDevice;
// Focus changes when we are currently holding focus.
private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
@Override
@@ -97,18 +99,20 @@
}
};
- public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
+ public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context,
+ BluetoothDevice device) {
mA2dpSinkSm = a2dpSinkSm;
mContext = context;
+ mDevice = device;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
@Override
public void handleMessage(Message message) {
if (DBG) {
- Log.d(TAG, " process message: " + message.what);
- Log.d(TAG, " audioFocus = " + mAudioFocus);
+ Log.d(TAG, " process message: " + message.what + ", device:" + mDevice);
}
+
switch (message.what) {
case SRC_STR_START:
mStreamAvailable = true;
@@ -120,10 +124,9 @@
}
// Audio stream has started, stop it if we don't have focus.
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- } else {
- startAvrcpUpdates();
+ requestAudioFocus();
}
+ startAvrcpUpdates();
break;
case SRC_STR_STOP:
@@ -155,12 +158,10 @@
startAvrcpUpdates();
break;
}
- // Otherwise, pause if we don't have focus
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- } else {
- startAvrcpUpdates();
+ requestAudioFocus();
}
+ startAvrcpUpdates();
break;
case SRC_PAUSE:
@@ -174,6 +175,10 @@
}
break;
+ case RELEASE_FOCUS:
+ abandonAudioFocus();
+ break;
+
case DISCONNECT:
// Remote device has disconnected, restore everything to default state.
stopAvrcpUpdates();
@@ -242,6 +247,7 @@
* Utility functions.
*/
private synchronized int requestAudioFocus() {
+ Log.d(TAG, "requestAudioFocus");
// Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
// type unknown.
AudioAttributes streamAttributes =
@@ -257,6 +263,7 @@
.setOnAudioFocusChangeListener(mAudioFocusListener, this)
.build();
int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
+ Log.d(TAG, "focusRequestStatus = " + focusRequestStatus);
// If the request is granted begin streaming immediately and schedule an upgrade.
if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
startAvrcpUpdates();
@@ -269,21 +276,24 @@
private synchronized void abandonAudioFocus() {
stopFluorideStreaming();
- mAudioManager.abandonAudioFocus(mAudioFocusListener);
- mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+ if (mAudioFocus != AudioManager.AUDIOFOCUS_NONE) {
+ Log.d(TAG, "abandoning audio focus");
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+ }
}
private void startFluorideStreaming() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
- mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+ A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+ A2dpSinkService.informAudioTrackGainNative(1.0f);
}
private void stopFluorideStreaming() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+ A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
private void setFluorideAudioTrackGain(float gain) {
- mA2dpSinkSm.informAudioTrackGainNative(gain);
+ A2dpSinkService.informAudioTrackGainNative(gain);
}
private void startAvrcpUpdates() {
@@ -293,8 +303,8 @@
if (DBG) {
Log.d(TAG, "startAvrcpUpdates");
}
- if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
- avrcpService.startAvrcpUpdates();
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
+ avrcpService.startAvrcpUpdates(mDevice);
} else {
Log.e(TAG, "startAvrcpUpdates failed because of connection.");
}
@@ -307,11 +317,12 @@
if (DBG) {
Log.d(TAG, "stopAvrcpUpdates");
}
- if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
- avrcpService.stopAvrcpUpdates();
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
+ avrcpService.stopAvrcpUpdates(mDevice);
} else {
Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
}
+
}
private void sendAvrcpPause() {
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
index 739f17c..8e072ba 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -88,6 +88,8 @@
private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
// Message sent when folder list is fetched.
private static final int MSG_FOLDER_LIST = 9;
+ // Message sent when streaming device is updated after soft-handoff
+ private static final int MSG_DEVICE_UPDATED = 10;
// Custom actions for PTS testing.
private static final String CUSTOM_ACTION_VOL_UP =
@@ -113,6 +115,9 @@
private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
| PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ public static final String ACTION_DEVICE_UPDATED =
+ "android.bluetooth.a2dp.mbs.action.DeviceUpdated";
+
private static final class AvrcpCommandQueueHandler extends Handler {
WeakReference<A2dpMediaBrowserService> mInst;
@@ -156,6 +161,8 @@
case MSG_FOLDER_LIST:
inst.msgFolderList((Intent) msg.obj);
break;
+ case MSG_DEVICE_UPDATED:
+ inst.msgDeviceUpdated((BluetoothDevice) msg.obj);
default:
Log.e(TAG, "Message not handled " + msg);
}
@@ -182,6 +189,7 @@
filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
+ filter.addAction(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
registerReceiver(mBtReceiver, filter);
synchronized (this) {
@@ -301,7 +309,7 @@
mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
// Since we request explicit playback here we should start the updates to UI.
- mAvrcpCtrlSrvc.startAvrcpUpdates();
+ mAvrcpCtrlSrvc.startAvrcpUpdates(mA2dpDevice);
}
// TRACK_EVENT should be fired eventually and the UI should be hence updated.
@@ -370,6 +378,8 @@
new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
} else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
+ } else if (ACTION_DEVICE_UPDATED.equals(action)) {
+ mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_UPDATED, btDev).sendToTarget();
}
}
};
@@ -386,6 +396,22 @@
refreshInitialPlayingState();
}
+ private synchronized void msgDeviceUpdated(BluetoothDevice device) {
+ if (device != null && device.equals(mA2dpDevice)) {
+ return;
+ }
+ Log.d(TAG, "msgDeviceUpdated. Previous: " + mA2dpDevice + " New: " + device);
+ // 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;
+ }
+ if (mAvrcpCtrlSrvc.isBrowsingConnected(device))
+ notifyChildrenChanged("__ROOT__");
+ refreshInitialPlayingState();
+ }
// Refresh the UI if we have a connected device and AVRCP is initialized.
private synchronized void refreshInitialPlayingState() {
@@ -400,11 +426,10 @@
return;
}
- if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
+ if (mA2dpDevice != null && !devices.contains(mA2dpDevice)) {
Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
return;
}
- mA2dpDevice = devices.get(0);
mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 1d4810a..178ed07 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -42,7 +42,7 @@
public class AddressedMediaPlayer {
private static final String TAG = "AddressedMediaPlayer";
- private static final Boolean DEBUG = false;
+ private static final Boolean DEBUG = true;
private static final long SINGLE_QID = 1;
private static final String UNKNOWN_TITLE = "(unknown)";
@@ -51,6 +51,7 @@
"com.google.android.music.mediasession.music_metadata";
private AvrcpMediaRspInterface mMediaInterface;
+ private Avrcp_ext mAvrcp = null;
@NonNull private List<MediaSession.QueueItem> mNowPlayingList;
private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
@@ -64,6 +65,11 @@
mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
}
+ public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface, Avrcp_ext mAvrcp_ext) {
+ this(mediaInterface);
+ mAvrcp = mAvrcp_ext;
+ }
+
void cleanup() {
if (DEBUG) {
Log.v(TAG, "cleanup");
@@ -258,11 +264,44 @@
Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
long qid = getActiveQueueItemId(mediaController);
byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+ Log.d(TAG, "qid: " + qid );
+ if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+ (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+ (qid != mLastTrackIdSent)) {
+ byte[] lastTrack =
+ ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+ mMediaInterface.trackChangedRsp(type, lastTrack);
+ type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ }
// The nowPlayingList changed: the new list has the full data for the current item
+ Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
mMediaInterface.trackChangedRsp(type, track);
mLastTrackIdSent = qid;
}
+ void sendTrackChangeWithId(int type, @Nullable MediaController mediaController, byte[] bdaddr) {
+ Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
+ if(mAvrcp == null) {
+ sendTrackChangeWithId(type, mediaController);
+ return;
+ }
+ long qid = getActiveQueueItemId(mediaController);
+ byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+ Log.d(TAG, "qid: " + qid );
+ if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+ (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+ (qid != mLastTrackIdSent)) {
+ byte[] lastTrack =
+ ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+ mAvrcp.trackChangedAddressedRsp(type, lastTrack, bdaddr);
+ type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ }
+ // The nowPlayingList changed: the new list has the full data for the current item
+ Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
+ mAvrcp.trackChangedAddressedRsp(type, track, bdaddr);
+ mLastTrackIdSent = qid;
+ }
+
/*
* helper method to check if startItem and endItem index is with range of
* MediaItem list. (Resultset containing all items in current path)
@@ -436,6 +475,8 @@
} else {
attrValue = desc.getTitle().toString();
}
+ if (attrValue == null)
+ attrValue = "<Unknown Title>";
break;
case AvrcpConstants.ATTRID_ARTIST:
@@ -465,8 +506,8 @@
break;
case AvrcpConstants.ATTRID_COVER_ART:
- Log.e(TAG, "getAttrValue: Cover art attribute not supported");
- return null;
+ attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+ break;
default:
Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
@@ -482,7 +523,7 @@
}
}
if (DEBUG) {
- Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
+ Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id: " + attr);
}
return attrValue;
}
@@ -551,8 +592,7 @@
return MediaSession.QueueItem.UNKNOWN_ID;
}
PlaybackState state = controller.getPlaybackState();
- if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
- || state.getState() == PlaybackState.STATE_NONE) {
+ if (state == null || state.getState() == PlaybackState.STATE_NONE) {
return MediaSession.QueueItem.UNKNOWN_ID;
}
long qid = state.getActiveQueueItemId();
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index e30ad41..2938582 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.graphics.Color;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -44,14 +45,21 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserManager;
import android.util.Log;
import android.view.KeyEvent;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -62,6 +70,11 @@
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import android.os.SystemProperties;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
/******************************************************************************
* support Bluetooth AVRCP profile. support metadata, play status, event
@@ -69,9 +82,14 @@
******************************************************************************/
public final class Avrcp {
- private static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
private static final String TAG = "Avrcp";
private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
+ private static final String AVRCP_VERSION_PROPERTY = "persist.bluetooth.avrcpversion";
+ private static final String AVRCP_1_4_STRING = "avrcp14";
+ private static final String AVRCP_1_5_STRING = "avrcp15";
+ private static final String AVRCP_1_6_STRING = "avrcp16";
+ private static final String AVRCP_NOTIFICATION_ID = "avrcp_notification";
private Context mContext;
private final AudioManager mAudioManager;
@@ -93,6 +111,7 @@
private int mTrackChangedNT;
private int mPlayPosChangedNT;
private int mAddrPlayerChangedNT;
+ private int mAvailablePlayersChangedNT;
private int mReportedPlayerID;
private int mNowPlayingListChangedNT;
private long mPlaybackIntervalMs;
@@ -103,11 +122,16 @@
private int mRemoteVolume;
private int mLastRemoteVolume;
private int mInitialRemoteVolume;
+ private NotificationManager mNotificationManager;
/* Local volume in audio index 0-15 */
private int mLocalVolume;
private int mLastLocalVolume;
private int mAbsVolThreshold;
+ private int mLastPassthroughcmd;
+
+ private boolean mFastforward;
+ private boolean mRewind;
private String mAddress;
private HashMap<Integer, Integer> mVolumeMapping;
@@ -132,6 +156,7 @@
public static final int BTRC_FEAT_METADATA = 0x01;
public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
public static final int BTRC_FEAT_BROWSE = 0x04;
+ public static final int BTRC_FEAT_AVRC_UI_UPDATE = 0x08;
/* AVRC response codes, from avrc_defs */
private static final int AVRC_RSP_NOT_IMPL = 8;
@@ -163,11 +188,13 @@
private static final int MSG_ABS_VOL_TIMEOUT = 17;
private static final int MSG_SET_A2DP_AUDIO_STATE = 18;
private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19;
+ private final static int MESSAGE_SET_MEDIA_SESSION = 24;
private static final int CMD_TIMEOUT_DELAY = 2000;
private static final int MAX_ERROR_RETRY_TIMES = 6;
private static final int AVRCP_MAX_VOL = 127;
private static final int AVRCP_BASE_VOLUME_STEP = 1;
+ private static final int SET_MEDIA_SESSION_DELAY = 300;
/* Communicates with MediaPlayer to fetch media content */
private BrowsedMediaPlayer mBrowsedMediaPlayer;
@@ -185,6 +212,8 @@
/* Manage browsed players */
private AvrcpBrowseManager mAvrcpBrowseManager;
+ /* BIP Responder */
+ static private AvrcpBipRsp mAvrcpBipRsp;
/* Broadcast receiver for device connections intent broadcasts */
private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
@@ -194,6 +223,7 @@
private EvictingQueue<MediaKeyLog> mPassthroughLogs; // Passthorugh keys dispatched
private List<MediaKeyLog> mPassthroughPending; // Passthrough keys sent not dispatched yet
private int mPassthroughDispatched; // Number of keys dispatched
+ private boolean pts_test = false;
private class MediaKeyLog {
private long mTimeSent;
@@ -252,6 +282,7 @@
mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mPlaybackIntervalMs = 0L;
mLastReportedPosition = -1;
@@ -264,6 +295,9 @@
mLastDirection = 0;
mVolCmdSetInProgress = false;
mAbsVolRetryTimes = 0;
+ mFastforward = false;
+ mRewind = false;
+ mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
mLocalVolume = -1;
mLastLocalVolume = -1;
mAbsVolThreshold = 0;
@@ -274,7 +308,7 @@
mContext = context;
mLastUsedPlayerID = 0;
mAddressedMediaPlayer = null;
-
+ mAvrcpBipRsp = null;
initNative();
mMediaSessionManager =
@@ -308,6 +342,20 @@
IntentFilter bootFilter = new IntentFilter();
bootFilter.addAction(Intent.ACTION_USER_UNLOCKED);
context.registerReceiver(mBootReceiver, bootFilter);
+ pts_test = SystemProperties.getBoolean("vendor.bluetooth.avrcpct-passthrough.pts", false);
+
+
+ // create Notification channel.
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationChannel mChannel = new NotificationChannel(AVRCP_NOTIFICATION_ID,
+ mContext.getString(R.string.avrcp_notification_name),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mChannel.setDescription(mContext.getString(R.string.bluetooth_advanced_feat_description));
+ mChannel.enableLights(true);
+ mChannel.setLightColor(Color.GREEN);
+ mNotificationManager.createNotificationChannel(mChannel);
+
}
private synchronized void start() {
@@ -332,6 +380,27 @@
}
mPackageManager = mContext.getApplicationContext().getPackageManager();
+ boolean isCoverArtSupported = false;
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if ((adapterService != null) && (adapterService.isVendorIntfEnabled())) {
+ if (adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_0103_SUPPORT))
+ {
+ isCoverArtSupported = false;
+ } else if(adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_COVERART_SUPPORT)){
+ isCoverArtSupported = true;
+ }
+ }
+ String avrcpVersion = SystemProperties.get(AVRCP_VERSION_PROPERTY, AVRCP_1_4_STRING);
+ if (DEBUG) Log.d(TAG, "avrcpVersion: " + avrcpVersion
+ + " isCoverArtSupported :"+isCoverArtSupported);
+ /* Enable Cover Art support is version is 1.6 and flag is set in config */
+ if (isCoverArtSupported && avrcpVersion != null &&
+ avrcpVersion.equals(AVRCP_1_6_STRING)) {
+ mAvrcpBipRsp = new AvrcpBipRsp(mContext);
+ mAvrcpBipRsp.start();
+ if (DEBUG) Log.d(TAG, "Starting AVRCP BIP Responder Service");
+ }
+
/* create object to communicate with addressed player */
mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
@@ -384,13 +453,19 @@
if (looper != null) {
looper.quitSafely();
}
-
+ if (mAvrcpBipRsp != null) {
+ mAvrcpBipRsp.stop();
+ mAvrcpBipRsp = null;
+ }
mAudioManagerPlaybackHandler = null;
mContext.unregisterReceiver(mAvrcpReceiver);
mContext.unregisterReceiver(mBootReceiver);
mAddressedMediaPlayer.cleanup();
mAvrcpBrowseManager.cleanup();
+
+ if (mNotificationManager != null )
+ mNotificationManager.deleteNotificationChannel(AVRCP_NOTIFICATION_ID);
}
public void cleanup() {
@@ -498,10 +573,35 @@
mRemoteVolume = -1;
mLocalVolume = -1;
mInitialRemoteVolume = -1;
+ mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
mAddress = address;
if (mVolumeMapping != null) {
mVolumeMapping.clear();
}
+
+ Log.d(TAG, "avrcpct-passthrough pts_test = " + pts_test);
+ if (pts_test) {
+ Log.v(TAG,"fake BTRC_FEAT_ABSOLUTE_VOL remote feat support for pts test");
+ mFeatures = mFeatures | BTRC_FEAT_ABSOLUTE_VOLUME;
+ }
+
+ if ((mFeatures & BTRC_FEAT_AVRC_UI_UPDATE) != 0) {
+ int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+ Notification notification = new Notification.Builder(mContext)
+ .setContentTitle(mContext.getString(R.string.bluetooth_rc_feat_title))
+ .setContentText(mContext.getString(R.string.bluetooth_rc_feat_content))
+ .setSubText(mContext.getString(R.string.bluetooth_rc_feat_subtext))
+ .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+ .setChannelId(AVRCP_NOTIFICATION_ID)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .build();
+
+ if (mNotificationManager != null )
+ mNotificationManager.notify(NOTIFICATION_ID, notification);
+ else
+ Log.e(TAG,"mNotificationManager is null");
+ Log.v(TAG," update notification manager on remote repair request");
+ }
break;
}
@@ -594,7 +694,10 @@
// convert remote volume to local volume
int volIndex = convertToAudioStreamVolume(absVol);
+ boolean isShowUI = true;
if (mInitialRemoteVolume == -1) {
+ //Don't show media UI when device connected.
+ isShowUI = false;
mInitialRemoteVolume = absVol;
if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax
&& volIndex > mAbsVolThreshold) {
@@ -613,7 +716,11 @@
if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT
|| msg.arg2 == AVRC_RSP_CHANGED || msg.arg2 == AVRC_RSP_INTERIM)) {
- /* If the volume has successfully changed */
+ if (msg.arg2 == AVRC_RSP_ACCEPT){
+ Log.d(TAG, "Don't show media UI when slide volume bar");
+ isShowUI = false;
+ }
+ /* If the volume has successfully changed */
mLocalVolume = volIndex;
if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
if (mLastLocalVolume != volIndex) {
@@ -628,7 +735,7 @@
}
}
- notifyVolumeChanged(mLocalVolume);
+ notifyVolumeChanged(mLocalVolume, isShowUI);
mRemoteVolume = absVol;
long pecentVolChanged = ((long) absVol * 100) / 0x7f;
Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
@@ -806,6 +913,12 @@
handlePassthroughCmd(msg.arg1, msg.arg2);
break;
+ case MESSAGE_SET_MEDIA_SESSION:
+ android.media.session.MediaController mMediaController =
+ (android.media.session.MediaController)msg.obj;
+ setActiveMediaSession(mMediaController);
+ break;
+
default:
Log.e(TAG, "unknown message! msg.what=" + msg.what);
break;
@@ -889,6 +1002,7 @@
private String mMediaTotalNumber;
private String mGenre;
private long mPlayingTimeMs;
+ private String coverArt;
private static final int ATTR_TITLE = 1;
private static final int ATTR_ARTIST_NAME = 2;
@@ -897,6 +1011,7 @@
private static final int ATTR_MEDIA_TOTAL_NUMBER = 5;
private static final int ATTR_GENRE = 6;
private static final int ATTR_PLAYING_TIME_MS = 7;
+ private static final int ATTR_COVER_ART = 8;
MediaAttributes(MediaMetadata data) {
@@ -905,13 +1020,27 @@
return;
}
+ String CurrentPackageName = (mMediaController != null) ?
+ mMediaController.getPackageName():null;
mArtistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST));
mAlbumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM));
- mMediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+ if (CurrentPackageName != null && !(CurrentPackageName.equals("com.android.music"))) {
+ mMediaNumber =
+ longStringOrBlank((data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)));
+ } else {
+ /* playlist starts with 0 for default player*/
+ mMediaNumber =
+ longStringOrBlank((data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + 1L));
+ }
mMediaTotalNumber =
longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
mGenre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
mPlayingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ if (mAvrcpBipRsp != null) {
+ coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
+ } else {
+ coverArt = stringOrBlank(null);
+ }
// Try harder for the title.
mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
@@ -926,6 +1055,11 @@
}
}
+ if (mTitle != null && CurrentPackageName != null &&
+ CurrentPackageName.equals("com.tencent.qqmusic")) {
+ mTitle = mTitle.trim();
+ }
+
if (mTitle == null) {
mTitle = new String();
}
@@ -954,7 +1088,8 @@
return (mTitle.equals(other.mTitle)) && (mArtistName.equals(other.mArtistName))
&& (mAlbumName.equals(other.mAlbumName)) && (mMediaNumber.equals(
other.mMediaNumber)) && (mMediaTotalNumber.equals(other.mMediaTotalNumber))
- && (mGenre.equals(other.mGenre)) && (mPlayingTimeMs == other.mPlayingTimeMs);
+ && (mGenre.equals(other.mGenre)) && (mPlayingTimeMs == other.mPlayingTimeMs)
+ && (coverArt.equals(other.coverArt));
}
public String getString(int attrId) {
@@ -977,6 +1112,15 @@
return mGenre;
case ATTR_PLAYING_TIME_MS:
return Long.toString(mPlayingTimeMs);
+ case ATTR_COVER_ART:
+ if (mAvrcpBipRsp != null) {
+ /* Fetch coverArt Handle now in case OBEX channel is established just
+ * before retrieving get element attribute. */
+ coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
+ } else {
+ coverArt = stringOrBlank(null);
+ }
+ return coverArt;
default:
return new String();
}
@@ -998,7 +1142,7 @@
return "[MediaAttributes: " + mTitle + " - " + mAlbumName + " by " + mArtistName + " ("
+ mPlayingTimeMs + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre
- + "]";
+ + "- " + coverArt + "]";
}
public String toRedactedString() {
@@ -1027,8 +1171,8 @@
byte newPlayStatus = getBluetoothPlayState(newState);
- if (newState.getState() != PlaybackState.STATE_BUFFERING
- && newState.getState() != PlaybackState.STATE_NONE) {
+ if (newState != null && newState.getState() != PlaybackState.STATE_BUFFERING
+ && newState.getState() != PlaybackState.STATE_NONE) {
long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
if (newState != null) {
newQueueId = newState.getActiveQueueItemId();
@@ -1040,18 +1184,27 @@
}
if (mAvailablePlayerViewChanged) {
- registerNotificationRspAvalPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+ Log.v(TAG, "Sending response for available playerchanged:");
+ if (mAvailablePlayersChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+ registerNotificationRspAvalPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+ mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ }
mAvailablePlayerViewChanged = false;
return;
}
- if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
- && mReportedPlayerID != mCurrAddrPlayerID) {
- registerNotificationRspAvalPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
- registerNotificationRspAddrPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
+ if (mReportedPlayerID != mCurrAddrPlayerID) {
+ if (mAvailablePlayersChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+ registerNotificationRspAvalPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+ mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ }
+ if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+ registerNotificationRspAddrPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED,
+ mCurrAddrPlayerID, sUIDCounter);
+ }
mAvailablePlayerViewChanged = false;
mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
@@ -1078,8 +1231,7 @@
// - Queue ID is valid and different from last Queue ID sent
if ((newQueueId == -1 || newQueueId != mLastQueueId)
&& mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
- && !currentAttributes.equals(mMediaAttributes)
- && newPlayStatus == PLAYSTATUS_PLAYING) {
+ && !currentAttributes.equals(mMediaAttributes)) {
Log.v(TAG, "Send track changed");
mMediaAttributes = currentAttributes;
mLastQueueId = newQueueId;
@@ -1097,6 +1249,7 @@
if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM || (mReportedPlayStatus
!= newPlayStatus)) {
sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
+ mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
}
sendPlayPosNotificationRsp(false);
@@ -1162,8 +1315,22 @@
break;
case EVT_PLAY_POS_CHANGED:
+ if (param <= 0)
+ param = 1;
+
+ boolean isSplitA2dpEnabled = false;
+ long update_interval = 0L;
+ String offloadSupported = SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
+ if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
+ isSplitA2dpEnabled = true;
+ Log.v(TAG,"split enabled");
+ }
+ update_interval = (isSplitA2dpEnabled) ?
+ SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 3000L):
+ SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 1000L);
+
mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- mPlaybackIntervalMs = (long) param * 1000L;
+ mPlaybackIntervalMs = Math.max((long)param * 1000L, update_interval);
sendPlayPosNotificationRsp(true);
break;
@@ -1174,6 +1341,7 @@
}
registerNotificationRspAvalPlayerChangedNative(
AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
+ mAvailablePlayersChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
break;
case EVT_ADDR_PLAYER_CHANGED:
@@ -1221,6 +1389,9 @@
}
Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
+ Bundle data = new Bundle();
+ data.putByteArray("BdAddress", address);
+ msg.setData(data);
handler.sendMessage(msg);
}
@@ -1239,7 +1410,8 @@
MediaPlayerInfo info = getAddressedPlayerInfo();
// for non-browsable players or no player
- if (info != null && !info.isBrowseSupported()) {
+ if ((info != null && !info.isBrowseSupported()) ||
+ (mFeatures & BTRC_FEAT_BROWSE) == 0) {
byte[] track = AvrcpConstants.TRACK_IS_SELECTED;
if (!mMediaAttributes.mExists) {
track = AvrcpConstants.NO_TRACK_SELECTED;
@@ -1252,12 +1424,15 @@
}
private long getPlayPosition() {
+ long currPosition;
if (mCurrentPlayState == null) {
+ Log.d(TAG, "getPlayPosition, mCurrentPlayState is null");
return -1L;
}
if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
- return -1L;
+ Log.d(TAG, "getPlayPosition, currentPosition is unknown");
+ return (isPlayingState(mCurrentPlayState)) ? 0L : -1L;
}
if (isPlayingState(mCurrentPlayState)) {
@@ -1265,8 +1440,11 @@
(SystemClock.elapsedRealtime() - mCurrentPlayState.getLastPositionUpdateTime());
return sinceUpdate + mCurrentPlayState.getPosition();
}
-
- return mCurrentPlayState.getPosition();
+ currPosition = mCurrentPlayState.getPosition();
+ if (mMediaAttributes.mPlayingTimeMs >= 0 && currPosition > mMediaAttributes.mPlayingTimeMs) {
+ currPosition = mMediaAttributes.mPlayingTimeMs;
+ }
+ return currPosition;
}
private boolean isPlayingState(@Nullable PlaybackState state) {
@@ -1312,9 +1490,8 @@
}
debugLine += " State: " + mCurrentPlayState.getState();
}
- if (requested || (
- (mLastReportedPosition != playPositionMs) && (playPositionMs >= mNextPosMs) || (
- playPositionMs <= mPrevPosMs))) {
+ if (requested || ((mLastReportedPosition != playPositionMs) &&
+ ((playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs)))) {
if (!requested) {
mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
}
@@ -1375,6 +1552,7 @@
Message msg = handler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
handler.sendMessage(msg);
+ Log.v(TAG, "Exit setAbsoluteVolume");
}
/* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
@@ -1514,9 +1692,14 @@
handler.sendMessage(msg);
}
- private void notifyVolumeChanged(int volume) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ private void notifyVolumeChanged(int volume, boolean isShowUI) {
+ if (isShowUI) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+ AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ } else {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+ AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ }
}
private int convertToAudioStreamVolume(int volume) {
@@ -1712,36 +1895,43 @@
private void setAddressedPlayer(byte[] bdaddr, int selectedId) {
String functionTag = "setAddressedPlayer(" + selectedId + "): ";
- synchronized (mMediaPlayerInfoList) {
- if (mMediaPlayerInfoList.isEmpty()) {
- Log.w(TAG, functionTag + "no players, send no available players");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY);
- return;
- }
- if (!mMediaPlayerInfoList.containsKey(selectedId)) {
- Log.w(TAG, functionTag + "invalid id, sending response back ");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INV_PLAYER);
- return;
- }
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ if (mMediaPlayerInfoList.isEmpty()) {
+ Log.w(TAG, functionTag + "no players, send no available players");
+ setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY);
+ return;
+ }
+ if (selectedId == NO_PLAYER_ID) {
+ Log.w(TAG, functionTag + "Respond dummy pass response ");
+ setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+ return;
+ }
+ if (!mMediaPlayerInfoList.containsKey(selectedId)) {
+ Log.w(TAG, functionTag + "invalid id, sending response back ");
+ setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INV_PLAYER);
+ return;
+ }
- if (isPlayerAlreadyAddressed(selectedId)) {
+ if (isPlayerAlreadyAddressed(selectedId)) {
+ MediaPlayerInfo info = getAddressedPlayerInfo();
+ Log.i(TAG, functionTag + "player already addressed: " + info);
+ setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+ return;
+ }
+ // register new Media Controller Callback and update the current IDs
+ if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
+ Log.e(TAG, functionTag + "updateCurrentController failed!");
+ setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+ return;
+ }
+ // If we don't have a controller, try to launch the player
MediaPlayerInfo info = getAddressedPlayerInfo();
- Log.i(TAG, functionTag + "player already addressed: " + info);
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
- return;
- }
- // register new Media Controller Callback and update the current IDs
- if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
- Log.e(TAG, functionTag + "updateCurrentController failed!");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
- return;
- }
- // If we don't have a controller, try to launch the player
- MediaPlayerInfo info = getAddressedPlayerInfo();
- if (info.getMediaController() == null) {
- Intent launch = mPackageManager.getLaunchIntentForPackage(info.getPackageName());
- Log.i(TAG, functionTag + "launching player " + launch);
- mContext.startActivity(launch);
+ if (info.getMediaController() == null) {
+ Intent launch = mPackageManager.getLaunchIntentForPackage(info.getPackageName());
+ Log.i(TAG, functionTag + "launching player " + launch);
+ mContext.startActivity(launch);
+ }
}
}
setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
@@ -1795,19 +1985,28 @@
@Override
public void onActiveSessionsChanged(
List<android.media.session.MediaController> newControllers) {
+ if (newControllers.size() > 0) {
+ HeadsetService mService = HeadsetService.getHeadsetService();
+ if (mService != null && mService.isScoOrCallActive()) {
+ Log.d(TAG, "Ignoring session changed update because of MT call in progress");
+ return;
+ }
+ }
Set<String> updatedPackages = new HashSet<String>();
// Update the current players
- for (android.media.session.MediaController controller : newControllers) {
- String packageName = controller.getPackageName();
- if (DEBUG) {
- Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
+ synchronized (Avrcp.this) {
+ for (android.media.session.MediaController controller : newControllers) {
+ String packageName = controller.getPackageName();
+ if (DEBUG) {
+ Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
+ }
+ // Only use the first (highest priority) controller from each package
+ if (updatedPackages.contains(packageName)) {
+ continue;
+ }
+ addMediaPlayerController(controller);
+ updatedPackages.add(packageName);
}
- // Only use the first (highest priority) controller from each package
- if (updatedPackages.contains(packageName)) {
- continue;
- }
- addMediaPlayerController(controller);
- updatedPackages.add(packageName);
}
if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
@@ -1842,16 +2041,19 @@
addMediaPlayerPackage(packageName);
updateCurrentMediaState();
}
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- if (entry.getValue().getPackageName().equals(packageName)) {
- int newAddrID = entry.getKey();
- if (DEBUG) {
- Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
+
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ if (entry.getValue().getPackageName().equals(packageName)) {
+ int newAddrID = entry.getKey();
+ if (DEBUG) {
+ Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
+ }
+ updateCurrentController(newAddrID, mCurrBrowsePlayerID);
+ updateCurrentMediaState();
+ return;
}
- updateCurrentController(newAddrID, mCurrBrowsePlayerID);
- updateCurrentMediaState();
- return;
}
}
}
@@ -1862,15 +2064,36 @@
private void setActiveMediaSession(MediaSession.Token token) {
android.media.session.MediaController activeController =
new android.media.session.MediaController(mContext, token);
- if (activeController.getPackageName().equals("com.android.server.telecom")) {
+ if (activeController.getPackageName().contains("telecom")) {
Log.d(TAG, "Ignore active media session change to telecom");
return;
}
+ HeadsetService mService = HeadsetService.getHeadsetService();
+ if (mService != null && mService.isScoOrCallActive()) {
+ Log.v(TAG,"Ignore setActiveMediaSession for telecom, call in progress");
+ return;
+ }
+
+
+ if (mHandler.hasMessages(MESSAGE_SET_MEDIA_SESSION))
+ mHandler.removeMessages(MESSAGE_SET_MEDIA_SESSION);
if (DEBUG) {
Log.v(TAG, "Set active media session " + activeController.getPackageName());
}
- addMediaPlayerController(activeController);
- setAddressedMediaSessionPackage(activeController.getPackageName());
+ synchronized (Avrcp.this) {
+ addMediaPlayerController(activeController);
+ setAddressedMediaSessionPackage(activeController.getPackageName());
+ }
+ }
+
+ private void setActiveMediaSession(android.media.session.MediaController mController) {
+ HeadsetService mService = HeadsetService.getHeadsetService();
+ if ((mService != null && mService.isScoOrCallActive())) {
+ Log.w(TAG, "Ignore media session during call");
+ return;
+ }
+ addMediaPlayerController(mController);
+ setAddressedMediaSessionPackage(mController.getPackageName());
}
private boolean startBrowseService(byte[] bdaddr, String packageName) {
@@ -1945,32 +2168,34 @@
/* Initializes list of media players identified from session manager active sessions */
private void initMediaPlayersList() {
- synchronized (mMediaPlayerInfoList) {
- // Clearing old browsable player's list
- mMediaPlayerInfoList.clear();
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ // Clearing old browsable player's list
+ mMediaPlayerInfoList.clear();
- if (mMediaSessionManager == null) {
- if (DEBUG) {
- Log.w(TAG, "initMediaPlayersList: no media session manager!");
+ if (mMediaSessionManager == null) {
+ if (DEBUG) {
+ Log.w(TAG, "initMediaPlayersList: no media session manager!");
+ }
+ return;
}
- return;
- }
- List<android.media.session.MediaController> controllers =
- mMediaSessionManager.getActiveSessions(null);
- if (DEBUG) {
- Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
- }
- /* Initializing all media players */
- for (android.media.session.MediaController controller : controllers) {
- addMediaPlayerController(controller);
- }
+ List<android.media.session.MediaController> controllers =
+ mMediaSessionManager.getActiveSessions(null);
+ if (DEBUG) {
+ Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
+ }
+ /* Initializing all media players */
+ for (android.media.session.MediaController controller : controllers) {
+ addMediaPlayerController(controller);
+ }
- updateCurrentMediaState();
+ updateCurrentMediaState();
- if (mMediaPlayerInfoList.size() > 0) {
- // Set the first one as the Addressed Player
- updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
+ if (mMediaPlayerInfoList.size() > 0) {
+ // Set the first one as the Addressed Player
+ updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
+ }
}
}
}
@@ -1978,11 +2203,13 @@
private List<android.media.session.MediaController> getMediaControllers() {
List<android.media.session.MediaController> controllers =
new ArrayList<android.media.session.MediaController>();
- synchronized (mMediaPlayerInfoList) {
- for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
- MediaController controller = info.getMediaController();
- if (controller != null) {
- controllers.add(controller.getWrappedInstance());
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
+ MediaController controller = info.getMediaController();
+ if (controller != null) {
+ controllers.add(controller.getWrappedInstance());
+ }
}
}
}
@@ -2019,30 +2246,32 @@
Log.d(TAG, "Skip adding telecom to the media player info list");
return updated;
}
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- MediaPlayerInfo current = entry.getValue();
- int id = entry.getKey();
- if (info.getPackageName().equals(current.getPackageName())) {
- if (!current.equalView(info)) {
- // If we would present a different player, make it a new player
- // so that controllers know whether a player is browsable or not.
- mMediaPlayerInfoList.remove(id);
- currentRemoved = (mCurrAddrPlayerID == id);
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ MediaPlayerInfo current = entry.getValue();
+ int id = entry.getKey();
+ if (info.getPackageName().equals(current.getPackageName())) {
+ if (!current.equalView(info)) {
+ // If we would present a different player, make it a new player
+ // so that controllers know whether a player is browsable or not.
+ mMediaPlayerInfoList.remove(id);
+ currentRemoved = (mCurrAddrPlayerID == id);
+ break;
+ }
+ updateId = id;
+ updated = true;
break;
}
- updateId = id;
- updated = true;
- break;
}
+ if (updateId == -1) {
+ // New player
+ mLastUsedPlayerID++;
+ updateId = mLastUsedPlayerID;
+ mAvailablePlayerViewChanged = true;
+ }
+ mMediaPlayerInfoList.put(updateId, info);
}
- if (updateId == -1) {
- // New player
- mLastUsedPlayerID++;
- updateId = mLastUsedPlayerID;
- mAvailablePlayerViewChanged = true;
- }
- mMediaPlayerInfoList.put(updateId, info);
}
if (DEBUG) {
Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
@@ -2055,23 +2284,26 @@
/** Remove all players related to |packageName| from the media player info list */
private MediaPlayerInfo removeMediaPlayerInfo(String packageName) {
- synchronized (mMediaPlayerInfoList) {
- int removeKey = -1;
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- if (entry.getValue().getPackageName().equals(packageName)) {
- removeKey = entry.getKey();
- break;
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ int removeKey = -1;
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ if (entry.getValue().getPackageName().equals(packageName)) {
+ removeKey = entry.getKey();
+ break;
+ }
}
- }
- if (removeKey != -1) {
- if (DEBUG) {
- Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
+ if (removeKey != -1) {
+ if (DEBUG) {
+ Log.d(TAG, "remove #" + removeKey + ":"
+ + mMediaPlayerInfoList.get(removeKey));
+ }
+ mAvailablePlayerViewChanged = true;
+ return mMediaPlayerInfoList.remove(removeKey);
}
- mAvailablePlayerViewChanged = true;
- return mMediaPlayerInfoList.remove(removeKey);
- }
- return null;
+ return null;
+ }
}
}
@@ -2080,14 +2312,16 @@
if (controller == null) {
return;
}
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- MediaPlayerInfo info = entry.getValue();
- MediaController c = info.getMediaController();
- if (c != null && c.equals(controller)) {
- info.setMediaController(null);
- if (entry.getKey() == mCurrAddrPlayerID) {
- updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ MediaPlayerInfo info = entry.getValue();
+ MediaController c = info.getMediaController();
+ if (c != null && c.equals(controller)) {
+ info.setMediaController(null);
+ if (entry.getKey() == mCurrAddrPlayerID) {
+ updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
+ }
}
}
}
@@ -2108,13 +2342,13 @@
case PlaybackState.STATE_PLAYING:
return PLAYSTATUS_PLAYING;
- case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_STOPPED:
case PlaybackState.STATE_NONE:
case PlaybackState.STATE_CONNECTING:
return PLAYSTATUS_STOPPED;
case PlaybackState.STATE_PAUSED:
+ case PlaybackState.STATE_BUFFERING:
return PLAYSTATUS_PAUSED;
case PlaybackState.STATE_FAST_FORWARDING:
@@ -2156,6 +2390,8 @@
featureBitsList.add(AvrcpConstants.AVRC_PF_UID_UNIQUE_BIT_NO);
featureBitsList.add(AvrcpConstants.AVRC_PF_NOW_PLAY_BIT_NO);
featureBitsList.add(AvrcpConstants.AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO);
+ if (mAvrcpBipRsp != null)
+ featureBitsList.add(AvrcpConstants.AVRC_PF_COVER_ART_BIT_NO);
}
// converting arraylist to array for response
@@ -2195,8 +2431,10 @@
private String getPackageName(int id) {
MediaPlayerInfo player = null;
- synchronized (mMediaPlayerInfoList) {
- player = mMediaPlayerInfoList.getOrDefault(id, null);
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ player = mMediaPlayerInfoList.getOrDefault(id, null);
+ }
}
if (player == null) {
@@ -2228,8 +2466,10 @@
/* Returns the MediaPlayerInfo for the currently addressed media player */
private MediaPlayerInfo getAddressedPlayerInfo() {
- synchronized (mMediaPlayerInfoList) {
- return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
+ }
}
}
@@ -2238,114 +2478,131 @@
* null if package name not found in media players list
*/
private MediaPlayerInfo getMediaPlayerInfo(String packageName) {
- synchronized (mMediaPlayerInfoList) {
- if (mMediaPlayerInfoList.isEmpty()) {
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ if (mMediaPlayerInfoList.isEmpty()) {
+ if (DEBUG) {
+ Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
+ }
+ return null;
+ }
+
+ for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
+ if (packageName.equals(info.getPackageName())) {
+ if (DEBUG) {
+ Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
+ }
+ return info;
+ }
+ }
if (DEBUG) {
- Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
+ Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
}
return null;
}
-
- for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
- if (packageName.equals(info.getPackageName())) {
- if (DEBUG) {
- Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
- }
- return info;
- }
- }
- if (DEBUG) {
- Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
- }
- return null;
}
}
/* prepare media list & return the media player list response object */
private MediaPlayerListRsp prepareMediaPlayerRspObj() {
- synchronized (mMediaPlayerInfoList) {
- // TODO(apanicke): This hack will go away as soon as a developer
- // option to enable or disable player selection is created. Right
- // now this is needed to fix BMW i3 carkits and any other carkits
- // that might try to connect to a player that isnt the current
- // player based on this list
- int numPlayers = 1;
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ // TODO(apanicke): This hack will go away as soon as a developer
+ // option to enable or disable player selection is created. Right
+ // now this is needed to fix BMW i3 carkits and any other carkits
+ // that might try to connect to a player that isnt the current
+ // player based on this list
+ int numPlayers = 1;
- int[] playerIds = new int[numPlayers];
- byte[] playerTypes = new byte[numPlayers];
- int[] playerSubTypes = new int[numPlayers];
- String[] displayableNameArray = new String[numPlayers];
- byte[] playStatusValues = new byte[numPlayers];
- short[] featureBitMaskValues =
- new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
+ int[] playerIds = new int[numPlayers];
+ byte[] playerTypes = new byte[numPlayers];
+ int[] playerSubTypes = new int[numPlayers];
+ String[] displayableNameArray = new String[numPlayers];
+ byte[] playStatusValues = new byte[numPlayers];
+ short[] featureBitMaskValues =
+ new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
- // Reserve the first spot for the currently addressed player if
- // we have one
- int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- int idx = players;
- if (entry.getKey() == mCurrAddrPlayerID) {
- idx = 0;
- } else {
- continue; // TODO(apanicke): Remove, see above note
- }
- MediaPlayerInfo info = entry.getValue();
- playerIds[idx] = entry.getKey();
- playerTypes[idx] = info.getMajorType();
- playerSubTypes[idx] = info.getSubType();
- displayableNameArray[idx] = info.getDisplayableName();
- playStatusValues[idx] = info.getPlayStatus();
+ // Reserve the first spot for the currently addressed player if
+ // we have one
+ int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ int idx = players;
+ if (entry.getKey() == mCurrAddrPlayerID) {
+ idx = 0;
+ } else {
+ continue; // TODO(apanicke): Remove, see above note
+ }
+ MediaPlayerInfo info = entry.getValue();
+ playerIds[idx] = entry.getKey();
+ playerTypes[idx] = info.getMajorType();
+ playerSubTypes[idx] = info.getSubType();
+ displayableNameArray[idx] = info.getDisplayableName();
+ playStatusValues[idx] = info.getPlayStatus();
- short[] featureBits = info.getFeatureBitMask();
- for (int numBit = 0; numBit < featureBits.length; numBit++) {
- /* gives which octet this belongs to */
- byte octet = (byte) (featureBits[numBit] / 8);
- /* gives the bit position within the octet */
- byte bit = (byte) (featureBits[numBit] % 8);
- featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
- (1 << bit);
+ short[] featureBits = info.getFeatureBitMask();
+ for (int numBit = 0; numBit < featureBits.length; numBit++) {
+ /* gives which octet this belongs to */
+ byte octet = (byte) (featureBits[numBit] / 8);
+ /* gives the bit position within the octet */
+ byte bit = (byte) (featureBits[numBit] % 8);
+ featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet]
+ |= (1 << bit);
+ }
+
+ /* printLogs */
+ if (DEBUG) {
+ Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
+ + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
+ + " status: " + playStatusValues[idx]);
+ }
+
+ if (idx != 0) {
+ players++;
+ }
}
- /* printLogs */
if (DEBUG) {
- Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
- + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
- + " status: " + playStatusValues[idx]);
+ Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
}
- if (idx != 0) {
- players++;
- }
+ return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
+ AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
+ playStatusValues, featureBitMaskValues, displayableNameArray);
}
-
- if (DEBUG) {
- Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
- }
-
- return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
- AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
- playStatusValues, featureBitMaskValues, displayableNameArray);
}
}
/* build media player list and send it to remote. */
private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
MediaPlayerListRsp rspObj = null;
- synchronized (mMediaPlayerInfoList) {
- int numPlayers = mMediaPlayerInfoList.size();
- if (numPlayers == 0) {
- mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY,
- (short) 0, (byte) 0, 0, null, null, null, null, null, null);
- return;
+ synchronized (this) {
+ synchronized (mMediaPlayerInfoList) {
+ int numPlayers = mMediaPlayerInfoList.size();
+ if (numPlayers == 0) {
+ mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY,
+ (short) 0, (byte) 0, 0, null, null, null, null, null, null);
+ return;
+ }
+ if (folderObj.mStartItem >= numPlayers || folderObj.mStartItem >= 1) {
+ Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
+ + " > num of items = " + numPlayers);
+ mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
+ (short) 0, (byte) 0, 0, null, null, null, null, null, null);
+ return;
+ }
+ if (mCurrAddrPlayerID == NO_PLAYER_ID) {
+ short[] featureBitsArray = {0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x04,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ Log.i(TAG, "handleMediaPlayerListRsp: Send dummy player response");
+ mediaPlayerListRspNative(folderObj.mAddress, (int)AvrcpConstants.RSP_NO_ERROR,
+ (int)sUIDCounter, AvrcpConstants.BTRC_ITEM_PLAYER, 1, new int[] {0},
+ new byte[] {AvrcpConstants.PLAYER_TYPE_AUDIO}, new int[] {1},
+ new byte[] {PLAYSTATUS_STOPPED}, featureBitsArray,
+ new String[] {"Dummy Player"});
+ return;
+ }
+ rspObj = prepareMediaPlayerRspObj();
}
- if (folderObj.mStartItem >= numPlayers) {
- Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
- + " > num of items = " + numPlayers);
- mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
- (short) 0, (byte) 0, 0, null, null, null, null, null, null);
- return;
- }
- rspObj = prepareMediaPlayerRspObj();
}
if (DEBUG) {
Log.d(TAG, "handleMediaPlayerListRsp: sending " + rspObj.mNumItems + " players");
@@ -2359,6 +2616,8 @@
/* unregister to the old controller, update new IDs and register to the new controller */
private boolean updateCurrentController(int addrId, int browseId) {
boolean registerRsp = true;
+ int preAddrId = mCurrAddrPlayerID;
+ int preBrowseId = mCurrBrowsePlayerID;
updateNewIds(addrId, browseId);
@@ -2381,6 +2640,7 @@
mMediaController.registerCallback(mMediaControllerCb, mHandler);
} else {
registerRsp = false;
+ updateNewIds(preAddrId, preBrowseId);
}
}
}
@@ -2441,6 +2701,13 @@
}
private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
+ HeadsetService mService = HeadsetService.getHeadsetService();
+ if ((mService != null) && mService.isScoOrCallActive()) {
+ Log.w(TAG, "Remote requesting play item while call is active");
+ playItemRspNative(bdaddr, AvrcpConstants.RSP_MEDIA_IN_USE);
+ return;
+ }
+
if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
mAddressedMediaPlayer.playItem(bdaddr, uid, mMediaController);
} else {
@@ -2462,10 +2729,9 @@
private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
if (itemAttr.mUidCounter != sUIDCounter) {
- Log.e(TAG, "handleGetItemAttr: invaild uid counter.");
- getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_UID_CHANGED, (byte) 0, null,
- null);
- return;
+ itemAttr.mUidCounter = sUIDCounter;
+ Log.e(TAG, "handleGetItemAttr: invalid uid counter, assign new value = "
+ + itemAttr.mUidCounter);
}
if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
if (mCurrAddrPlayerID == NO_PLAYER_ID) {
@@ -2490,8 +2756,10 @@
// for scope as media player list
if (scope == AvrcpConstants.BTRC_SCOPE_PLAYER_LIST) {
int numPlayers = 0;
- synchronized (mMediaPlayerInfoList) {
- numPlayers = mMediaPlayerInfoList.size();
+ synchronized(this) {
+ synchronized (mMediaPlayerInfoList) {
+ numPlayers = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
+ }
}
if (DEBUG) {
Log.d(TAG, "handleGetTotalNumOfItemsResponse: " + numPlayers + " players.");
@@ -2580,12 +2848,14 @@
}
ProfileService.println(sb, "");
ProfileService.println(sb, "Media Players:");
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- int key = entry.getKey();
- ProfileService.println(sb,
- ((mCurrAddrPlayerID == key) ? " *#" : " #") + entry.getKey() + ": " + entry
- .getValue());
+ synchronized(this) {
+ synchronized (mMediaPlayerInfoList) {
+ for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+ int key = entry.getKey();
+ ProfileService.println(sb,
+ ((mCurrAddrPlayerID == key) ? " *#" : " #") + entry.getKey() + ": "
+ + entry.getValue());
+ }
}
}
@@ -2850,9 +3120,60 @@
if (state == AvrcpConstants.KEY_STATE_RELEASE) {
action = KeyEvent.ACTION_UP;
}
+
+ if (mLastPassthroughcmd == KeyEvent.KEYCODE_UNKNOWN) {
+ if (isPlayingState(mCurrentPlayState) && mAudioManager.isMusicActive() &&
+ (mA2dpState == BluetoothA2dp.STATE_PLAYING) &&
+ (code == KeyEvent.KEYCODE_MEDIA_PLAY)) {
+ Log.w(TAG, "Ignoring passthrough command play" + op + " state " + state +
+ "in music playing");
+ return;
+ }
+ if (!isPlayingState(mCurrentPlayState) && (!mAudioManager.isMusicActive())
+ && (mA2dpState == BluetoothA2dp.STATE_NOT_PLAYING) &&
+ (code == KeyEvent.KEYCODE_MEDIA_PAUSE)) {
+ Log.w(TAG, "Ignoring passthrough command pause" + op + " state " + state +
+ "in music playing");
+ return;
+ }
+ }
+
KeyEvent event = new KeyEvent(action, code);
if (!KeyEvent.isMediaKey(code)) {
Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
+ } else {
+ if (code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
+ if (action == KeyEvent.ACTION_DOWN) {
+ mFastforward = true;
+ } else {
+ mFastforward = false;
+ }
+ } else if (code == KeyEvent.KEYCODE_MEDIA_REWIND) {
+ if (action == KeyEvent.ACTION_DOWN) {
+ mRewind = true;
+ } else {
+ mRewind = false;
+ }
+ } else {
+ mRewind = false;
+ mFastforward = false;
+ }
+ }
+ /* IOT Fix as some remote recognise FF/Rewind state as non-playing hence send
+ * changed response at the time of Release of Fast-Forward/Rewind Button */
+ if ((code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || code == KeyEvent.KEYCODE_MEDIA_REWIND)
+ && (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM)
+ && (action == KeyEvent.ACTION_UP)) {
+ sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mReportedPlayStatus);
+ Log.d(TAG, "Sending playback status CHANGED rsp on FF/Rewind key release");
+ mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
+ }
+
+ Log.d(TAG, "cached passthrough: " + mLastPassthroughcmd + "current passthrough: " + code);
+ if ((mLastPassthroughcmd != KeyEvent.KEYCODE_UNKNOWN) && (mLastPassthroughcmd != code)) {
+ mLastPassthroughcmd = KeyEvent.KEYCODE_UNKNOWN;
+ } else {
+ mLastPassthroughcmd = code;
}
mMediaSessionManager.dispatchMediaKeyEvent(event);
diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
index 50a2eeb..94419ae 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpConstants.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
@@ -16,6 +16,8 @@
package com.android.bluetooth.avrcp;
+import android.util.Log;
+
/*************************************************************************************************
* Grouped all HAL constants into a file to be consistent with the stack.
* Moved the constants used in Avrcp to this new file to be used across multiple files.
@@ -145,7 +147,7 @@
static final short AVRC_PF_UID_UNIQUE_BIT_NO = 62;
static final short AVRC_PF_NOW_PLAY_BIT_NO = 65;
static final short AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO = 67;
-
+ static final short AVRC_PF_COVER_ART_BIT_NO = 68;
static final byte PLAYER_TYPE_AUDIO = 1;
static final int PLAYER_SUBTYPE_NONE = 0;
@@ -162,4 +164,16 @@
static final int KEY_STATE_PRESS = 1;
static final int KEY_STATE_RELEASE = 0;
+
+ static final int GET_ATTRIBUTE_IDS = 0;
+ static final int GET_VALUE_IDS = 1;
+ static final int GET_ATTRIBUTE_TEXT = 2;
+ static final int GET_VALUE_TEXT = 3;
+ static final int GET_ATTRIBUTE_VALUES = 4;
+ static final int NOTIFY_ATTRIBUTE_VALUES = 5;
+ static final int SET_ATTRIBUTE_VALUES = 6;
+ static final int GET_INVALID = 0xff;
+
+ public static final String TAG = "Avrcp";
+ public static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
}
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
index 0889d90..0d8c45e 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
@@ -40,7 +40,7 @@
************************************************************************************************/
class BrowsedMediaPlayer {
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final String TAG = "BrowsedMediaPlayer";
/* connection state with MediaBrowseService */
@@ -48,8 +48,10 @@
private static final int CONNECTED = 1;
private static final int SUSPENDED = 2;
+ private static final int BROWSED_ITEM_ID_INDEX = 2;
+ private static final int BROWSED_FOLDER_ID_INDEX = 4;
private static final String[] ROOT_FOLDER = {"root"};
-
+ private static boolean mPlayerRoot = false;
/* package and service name of target Media Player which is set for browsing */
private String mPackageName;
private String mConnectingPackageName;
@@ -58,6 +60,9 @@
private AvrcpMediaRspInterface mMediaInterface;
private byte[] mBDAddr;
+ private String mCurrentBrowsePackage;
+ private String mCurrentBrowseClass;
+
/* Object used to connect to MediaBrowseService of Media Player */
private MediaBrowser mMediaBrowser = null;
private MediaController mMediaController = null;
@@ -69,12 +74,15 @@
/* stores the path trail during changePath */
private Stack<String> mPathStack = null;
-
+ private Stack<String> mLocalPathCache = null;
/* Number of items in current folder */
private int mCurrFolderNumItems = 0;
- /* store mapping between uid(Avrcp) and mediaId(Media Player). */
- private HashMap<Integer, String> mHmap = new HashMap<Integer, String>();
+ /* store mapping between uid(Avrcp) and mediaId(Media Player) for Media Item */
+ private HashMap<Integer, String> mMediaHmap = new HashMap<Integer, String>();
+
+ /* store mapping between uid(Avrcp) and mediaId(Media Player) for Folder Item */
+ private HashMap<Integer, String> mFolderHmap = new HashMap<Integer, String>();
/* command objects from avrcp handler */
private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
@@ -146,6 +154,7 @@
if (DEBUG) {
Log.d(TAG, "sending setbrowsed player rsp");
}
+ Log.w(TAG, "sending setbrowsed player rsp");
mFolderItems = children;
mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
(byte) 0x00, children.size(), ROOT_FOLDER);
@@ -155,6 +164,7 @@
mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
mCurrFolderNumItems);
}
+ refreshFolderItems(mFolderItems);
mMediaBrowser.unsubscribe(parentId);
}
@@ -280,8 +290,10 @@
/* initialize mediacontroller in order to communicate with media player. */
private void onBrowseConnect(String connectedPackage, MediaBrowser browser) {
if (!connectedPackage.equals(mConnectingPackageName)) {
- Log.w(TAG, "onBrowseConnect: recieved callback for package we aren't connecting to "
- + connectedPackage);
+ Log.w(TAG, "onBrowseConnect: recieved callback for package" + mConnectingPackageName +
+ "we aren't connecting to " + connectedPackage);
+ mMediaInterface.setBrowsedPlayerRsp(
+ mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
return;
}
mConnectingPackageName = null;
@@ -310,17 +322,40 @@
/* get rootfolder uid from media player */
if (mMediaId == null) {
mMediaId = mMediaBrowser.getRoot();
+ Log.d(TAG, "media browser root = " + mMediaId);
+
+ if (mMediaId == null || mMediaId.length() == 0) {
+ Log.e(TAG, "onBrowseConnect: root value is empty or null");
+ mMediaInterface.setBrowsedPlayerRsp(
+ mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+ return;
+ }
+
/*
* assuming that root folder uid will not change on uids changed
*/
mRootFolderUid = mMediaId;
/* store root folder uid to stack */
mPathStack.push(mMediaId);
+ String [] ExternalPath = mMediaId.split("/");
+ if (ExternalPath != null) {
+ Log.d(TAG,"external path length: " + ExternalPath.length);
+ if (ExternalPath.length == 1) {
+ mLocalPathCache.push(mMediaId);
+ } else if (ExternalPath.length == 0 && mMediaId.equals("/")) {
+ mPlayerRoot = true;
+ mLocalPathCache.push(mMediaId);
+ } else {
+ //to trim the root in GMP which comes as "com.google.android.music.generic/root"
+ mLocalPathCache.push(ExternalPath[ExternalPath.length - 1]);
+ }
+ }
+ /* get root folder items */
+ Log.e(TAG, "onBrowseConnect: subscribe event for FolderCb");
+ mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
}
mMediaController = MediaControllerFactory.make(mContext, token);
- /* get root folder items */
- mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
return;
}
} catch (NullPointerException ex) {
@@ -333,26 +368,120 @@
}
public void setBrowsed(String packageName, String cls) {
+ Log.w(TAG, "!! In setBrowse function !!" + mFolderItems);
+ if ((mPackageName != null && packageName != null
+ && !mPackageName.equals(packageName)) || (mFolderItems == null)) {
+ Log.d(TAG, "setBrowse for packageName = " + packageName);
+ mConnectingPackageName = packageName;
+ mPackageName = packageName;
+ mClassName = cls;
+
+ /* cleanup variables from previous browsed calls */
+ mFolderItems = null;
+ mMediaId = null;
+ mRootFolderUid = null;
+ mPlayerRoot = false;
+ /*
+ * create stack to store the navigation trail (current folder ID). This
+ * will be required while navigating up the folder
+ */
+ mPathStack = new Stack<String>();
+ mLocalPathCache = new Stack<String>();
+ /* Bind to MediaBrowseService of MediaPlayer */
+ MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+ MediaBrowser tempBrowser = new MediaBrowser(
+ mContext, new ComponentName(packageName, mClassName), callback, null);
+ callback.setBrowser(tempBrowser);
+ tempBrowser.connect();
+ } else if (mFolderItems != null) {
+ mPackageName = packageName;
+ mClassName = cls;
+ int rsp_status = AvrcpConstants.RSP_NO_ERROR;
+ int folder_depth = (mPathStack.size() > 0) ? (mPathStack.size() - 1) : 0;
+ if (!mPathStack.empty()) {
+ Log.d(TAG, "~~current Path = " + mPathStack.peek());
+ if (mPathStack.size() > 1) {
+ String top = mPathStack.peek();
+ mPathStack.pop();
+ String path = mPathStack.peek();
+ mPathStack.push(top);
+ String [] ExternalPath = path.split("/");
+ if (!mPlayerRoot && ExternalPath != null && ExternalPath.length > 1) {
+ Log.d(TAG,"external path length: " + ExternalPath.length);
+ String [] folderPath = new String[ExternalPath.length - 1];
+ for (int i = 0; i < (ExternalPath.length - 1); i++) {
+ folderPath[i] = ExternalPath[i + 1];
+ Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+ }
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+ (byte)folder_depth, mFolderItems.size(), folderPath);
+ } else if (!mPlayerRoot && mLocalPathCache.size() > 1 && ExternalPath.length == 1) {
+ String [] folderPath = new String[mLocalPathCache.size() - 1];
+ folderPath = mLocalPathCache.toArray(folderPath);
+ for (int i = 0; i < mLocalPathCache.size() - 1; i++) {
+ Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+ }
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+ (byte)folder_depth, mFolderItems.size(), folderPath);
+ } else if (mPlayerRoot && mLocalPathCache.size() > 1) {
+ String [] folderPath = new String[mLocalPathCache.size() - 1];
+ folderPath = mLocalPathCache.toArray(folderPath);
+ for (int i = 0; i < mLocalPathCache.size() - 1; i++) {
+ Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+ }
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+ (byte)folder_depth, mFolderItems.size(), folderPath);
+ } else {
+ Log.e(TAG, "sending internal error !!!");
+ rsp_status = AvrcpConstants.RSP_INTERNAL_ERR;
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)0x00, 0, null);
+ }
+ } else if (mPathStack.size() == 1) {
+ Log.d(TAG, "On root send SetBrowse response with root properties");
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)folder_depth,
+ mFolderItems.size(), ROOT_FOLDER);
+ }
+ } else {
+ Log.e(TAG, "Path Stack empty sending internal error !!!");
+ rsp_status = AvrcpConstants.RSP_INTERNAL_ERR;
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)0x00, 0, null);
+ }
+ Log.d(TAG, "send setbrowse rsp status=" + rsp_status + " folder_depth=" + folder_depth);
+ }
+ }
+
+ public void TryReconnectBrowse(String packageName, String cls) {
+ Log.w(TAG, "Try reconnection with Browser service for package = " + packageName);
mConnectingPackageName = packageName;
+ mPackageName = packageName;
mClassName = cls;
+
/* cleanup variables from previous browsed calls */
mFolderItems = null;
mMediaId = null;
mRootFolderUid = null;
- /*
- * create stack to store the navigation trail (current folder ID). This
- * will be required while navigating up the folder
- */
+ mPlayerRoot = false;
+
+ if (mPathStack != null)
+ mPathStack = null;
mPathStack = new Stack<String>();
- /* Bind to MediaBrowseService of MediaPlayer */
- MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
- MediaBrowser tempBrowser =
- new MediaBrowser(mContext, new ComponentName(packageName, mClassName), callback,
- null);
- callback.setBrowser(tempBrowser);
+ if (mLocalPathCache != null)
+ mLocalPathCache = null;
+ mLocalPathCache = new Stack<String>();
+ MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+ MediaBrowser tempBrowser = new MediaBrowser(
+ mContext, new ComponentName(packageName, cls), callback, null);
+ callback.setBrowser(tempBrowser);
tempBrowser.connect();
+ Log.w(TAG, "Reconnected with Browser service");
+ }
+
+ public void setCurrentPackage(String packageName, String cls) {
+ Log.w(TAG, "Set current Browse based on Addr Player as " + packageName);
+ mCurrentBrowsePackage = packageName;
+ mCurrentBrowseClass = cls;
}
/* called when connection to media player is closed */
@@ -362,13 +491,16 @@
}
if (mConnState != DISCONNECTED) {
- mMediaBrowser.disconnect();
+ if (mMediaBrowser != null) mMediaBrowser.disconnect();
}
- mHmap = null;
+ mMediaHmap = null;
+ mFolderHmap = null;
mMediaController = null;
mMediaBrowser = null;
mPathStack = null;
+ mLocalPathCache = null;
+ mPlayerRoot = false;
}
public boolean isPlayerConnected() {
@@ -403,7 +535,7 @@
/* check direction and change the path */
if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
- if ((newPath = byteToString(folderUid)) == null) {
+ if ((newPath = byteToStringFolder(folderUid)) == null) {
Log.e(TAG, "Could not get media item from folder Uid, sending err response");
mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0);
} else if (!isBrowsableFolderDn(newPath)) {
@@ -416,6 +548,26 @@
mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
} else {
mMediaBrowser.subscribe(newPath, mFolderItemsCb);
+ String [] ExternalPath = newPath.split("/");
+ if (ExternalPath != null) {
+ Log.d(TAG,"external path length: " + ExternalPath.length);
+ if (ExternalPath.length == 1) {
+ //when external path length is 1 then extract the folder name from index 4
+ String folder_name = parseQueueId(newPath, BROWSED_FOLDER_ID_INDEX);
+ Log.d(TAG,"folder path: " + folder_name);
+ if (folder_name != null) {
+ mLocalPathCache.push(folder_name);
+ } else {
+ mLocalPathCache.push(newPath);
+ }
+ } else {
+ String folderPath = ExternalPath[ExternalPath.length - 1];
+ if (folderPath != null) {
+ Log.d(TAG,"folder path: " + folderPath);
+ mLocalPathCache.push(folderPath);
+ }
+ }
+ }
/* assume that call is success and update stack with new folder path */
mPathStack.push(newPath);
}
@@ -429,6 +581,7 @@
} else {
/* move folder up */
mPathStack.pop();
+ mLocalPathCache.pop();
newPath = mPathStack.peek();
mMediaBrowser.subscribe(newPath, mFolderItemsCb);
}
@@ -445,7 +598,7 @@
}
/* check if uid is valid by doing a lookup in hashmap */
- mediaID = byteToString(itemAttr.mUid);
+ mediaID = byteToStringMedia(itemAttr.mUid);
if (mediaID == null) {
Log.e(TAG, "uid is invalid");
mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
@@ -492,18 +645,17 @@
}
public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
- if (!isPlayerConnected()) {
- Log.e(TAG, "unable to connect to media player, sending internal error");
- /* unable to connect to media player. Send error response to remote device */
- mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
- return;
- }
-
if (DEBUG) {
Log.d(TAG, "getFolderItemsVFS");
}
mFolderItemsReqObj = reqObj;
+ if ((mCurrentBrowsePackage != null) && (!mCurrentBrowsePackage.equals(mPackageName))) {
+ Log.w(TAG, "Try reconnection with Browser service as addressed pkg is changed = "
+ + mCurrentBrowsePackage + "from " + mPackageName);
+ TryReconnectBrowse(mCurrentBrowsePackage, mCurrentBrowseClass);
+ }
+
if (mFolderItems == null) {
/* Failed to fetch folder items from media player. Send error to remote device */
Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS");
@@ -523,7 +675,7 @@
if (isPlayerConnected()) {
/* check if uid is valid */
- if ((folderUid = byteToString(uid)) == null) {
+ if ((folderUid = byteToStringMedia(uid)) == null) {
Log.e(TAG, "uid is invalid!");
mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
return;
@@ -632,7 +784,12 @@
folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
}
/* set uid for current item */
- byte[] uid = stringToByte(item.getDescription().getMediaId());
+ byte[] uid;
+ if (folderDataNative.mItemTypes[itemIndex] == AvrcpConstants.BTRC_ITEM_MEDIA)
+ uid = stringToByteMedia(item.getDescription().getMediaId(), BROWSED_ITEM_ID_INDEX);
+ else
+ uid = stringToByteFolder(item.getDescription().getMediaId());
+
for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
}
@@ -731,8 +888,8 @@
break;
case AvrcpConstants.ATTRID_COVER_ART:
- Log.e(TAG, "getAttrValue: Cover art attribute not supported");
- return null;
+ attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+ break;
default:
Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
@@ -748,7 +905,7 @@
}
}
if (DEBUG) {
- Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
+ Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + " attr id: " + attr);
}
return attrValue;
}
@@ -781,24 +938,53 @@
return true;
}
- /* convert uid to mediaId */
- private String byteToString(byte[] byteArray) {
+ private String parseQueueId(String mediaId, int mId) {
+ if (isNumeric(mediaId)) {
+ Log.d(TAG, "Get queue id: " + mediaId);
+ return mediaId.trim();
+ } else {
+ String[] mediaIdItems = mediaId.split(",");
+ if (mediaIdItems != null && mediaIdItems.length > mId) {
+ Log.d(TAG, "Get queue id: " + mediaIdItems[mId]);
+ return mediaIdItems[mId].trim();
+ }
+ }
+
+ Log.d(TAG, "Unkown queue id");
+ return null;
+ }
+
+ /* convert uid to mediaId for Media item*/
+ private String byteToStringMedia(byte[] byteArray) {
int uid = new BigInteger(byteArray).intValue();
- String mediaId = mHmap.get(uid);
+ String mediaId = mMediaHmap.get(uid);
return mediaId;
}
- /* convert mediaId to uid */
- private byte[] stringToByte(String mediaId) {
+ /* convert uid to mediaId for Folder item*/
+ private String byteToStringFolder(byte[] byteArray) {
+ int uid = new BigInteger(byteArray).intValue();
+ String mediaId = mFolderHmap.get(uid);
+ return mediaId;
+ }
+
+ /* convert mediaId to uid for Media item*/
+ private byte[] stringToByteMedia(String mediaId, int id) {
/* check if this mediaId already exists in hashmap */
- if (!mHmap.containsValue(mediaId)) { /* add to hashmap */
- // Offset by one as uid 0 is reserved
- int uid = mHmap.size() + 1;
- mHmap.put(uid, mediaId);
+ if (!mMediaHmap.containsValue(mediaId)) { /* add to hashmap */
+ int uid;
+ String queueId = parseQueueId(mediaId, id);
+ if (queueId == null) {
+ uid = mMediaHmap.size() + 1;
+ } else {
+ uid = Integer.valueOf(queueId).intValue();
+ }
+
+ mMediaHmap.put(uid, mediaId);
return intToByteArray(uid);
} else { /* search key for give mediaId */
- for (int uid : mHmap.keySet()) {
- if (mHmap.get(uid).equals(mediaId)) {
+ for (int uid : mMediaHmap.keySet()) {
+ if (mMediaHmap.get(uid).equals(mediaId)) {
return intToByteArray(uid);
}
}
@@ -806,6 +992,37 @@
return null;
}
+ /* convert mediaId to uid for Folder item*/
+ private byte[] stringToByteFolder(String mediaId) {
+ /* check if this mediaId already exists in hashmap */
+ if (!mFolderHmap.containsValue(mediaId)) { /* add to hashmap */
+ // Offset by one as uid 0 is reserved
+ int uid = mFolderHmap.size() + 1;
+ mFolderHmap.put(uid, mediaId);
+ return intToByteArray(uid);
+ } else { /* search key for give mediaId */
+ for (int uid : mFolderHmap.keySet()) {
+ if (mFolderHmap.get(uid).equals(mediaId)) {
+ return intToByteArray(uid);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void refreshFolderItems(List<MediaBrowser.MediaItem> folderItems) {
+ for (int itemIndex = 0; itemIndex < folderItems.size(); itemIndex++) {
+ MediaBrowser.MediaItem item = folderItems.get(itemIndex);
+ int flags = item.getFlags();
+ if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+ Log.d(TAG, "Folder item, no need refresh hashmap from mediaId to uid");
+ } else {
+ Log.d(TAG, "Media item, refresh haspmap from mediaId to uid");
+ stringToByteMedia(item.getDescription().getMediaId(), BROWSED_ITEM_ID_INDEX);
+ }
+ }
+ }
+
/* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
private List<MediaBrowser.MediaItem> queueItem2MediaItem(
List<MediaSession.QueueItem> tempItems) {
@@ -839,4 +1056,19 @@
return encodedValue;
}
+
+ public boolean isNumeric(String mediaId) {
+ String trimStr = mediaId.trim();
+ int length = trimStr.length();
+
+ for(int i = 0; i < length; i++) {
+ char c = trimStr.charAt(i);
+ if (!((c >= '0' && c <= '9'))) {
+ Log.v(TAG, "Non-Numeric media Id");
+ return false;
+ }
+ }
+ Log.v(TAG, "Numeric media Id");
+ return true;
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 6111ec4..bf60f38 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothAvrcpController;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -27,25 +28,33 @@
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.os.SystemProperties;
import android.util.Log;
-import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
/**
* 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 String LOG_TAG = "AvrcpController";
+ static final boolean DBG = true;
+ static final boolean VDBG = Log.isLoggable(LOG_TAG, Log.VERBOSE);
/*
* Play State Values from JNI
*/
@@ -183,8 +192,8 @@
public static final int BROWSE_SCOPE_SEARCH = 0x02;
public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
- private AvrcpControllerStateMachine mAvrcpCtSm;
private static AvrcpControllerService sAvrcpControllerService;
+ private AdapterService mAdapterService;
// UID size is 8 bytes (AVRCP 1.6 spec)
private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
@@ -196,6 +205,12 @@
// (which also has no UID).
private String mCurrentBrowseFolderUID = null;
+ // HashMap of state machine of AVRCP Controller Connection
+ private static final ConcurrentMap<BluetoothDevice,
+ AvrcpControllerStateMachine> mStateMachines = new ConcurrentHashMap<>();
+
+ private static int mMaxAllowedAvrcpConnection = 1;
+
static {
classInitNative();
}
@@ -211,11 +226,13 @@
@Override
protected boolean start() {
- HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
- thread.start();
- mAvrcpCtSm = new AvrcpControllerStateMachine(this);
- mAvrcpCtSm.start();
+ Log.d(TAG, "start()");
+ mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+ "AdapterService cansinkstatenot be null when A2dpService starts");
+ mMaxAllowedAvrcpConnection = Math.min(
+ SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
+ A2dpSinkService.MAX_ALLOWED_SINK_CONNECTIONS);
setAvrcpControllerService(this);
return true;
}
@@ -223,8 +240,11 @@
@Override
protected boolean stop() {
setAvrcpControllerService(null);
- if (mAvrcpCtSm != null) {
- mAvrcpCtSm.doQuit();
+ synchronized (mStateMachines) {
+ for (AvrcpControllerStateMachine mAvrcpCtSm : mStateMachines.values()) {
+ mAvrcpCtSm.doQuit();
+ }
+ mStateMachines.clear();
}
return true;
}
@@ -253,8 +273,8 @@
public synchronized List<BluetoothDevice> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- if (mConnectedDevice != null) {
- devices.add(mConnectedDevice);
+ for (AvrcpControllerStateMachine mAvrcpSm: mStateMachines.values()) {
+ devices.add(mAvrcpSm.getDevice());
}
return devices;
}
@@ -264,33 +284,49 @@
*/
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);
+ List<BluetoothDevice> devices = new ArrayList<>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ synchronized (mStateMachines) {
+ for (BluetoothDevice device : bondedDevices) {
+ if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+ BluetoothUuid.AvrcpTarget)) {
+ continue;
+ }
+ AvrcpControllerStateMachine sm = mStateMachines.get(device);
+ if (sm != null) {
+ for (int i = 0; i < states.length; i++) {
+ if (BluetoothProfile.STATE_CONNECTED == states[i]) {
+ devices.add(device);
+ }
+ }
+ }
}
+ return devices;
}
- return devices;
}
public synchronized int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED
+ return (mStateMachines.get(device) != 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);
+ Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState
+ + ", device:" + device);
if (device == null) {
Log.e(TAG, "sendGroupNavigationCmd device is null");
}
- if (!(device.equals(mConnectedDevice))) {
- Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
+ if (!(mStateMachines.containsKey(device))) {
+ Log.e(TAG, " Device " + device + "does not match connected devices");
return;
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
keyCode, keyState, device);
@@ -298,30 +334,40 @@
}
public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+ Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState
+ + ", To: " + device);
if (device == null) {
- Log.e(TAG, "sendPassThroughCmd Device is null");
+ Log.e(TAG, "sendPassThroughCmd: Device is null");
return;
}
- if (!device.equals(mConnectedDevice)) {
- Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, " Device " + device + " does not match connected devices");
return;
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
keyCode, keyState, device);
mAvrcpCtSm.sendMessage(msg);
}
- public void startAvrcpUpdates() {
+ public void startAvrcpUpdates(BluetoothDevice device) {
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
.sendToTarget();
}
- public void stopAvrcpUpdates() {
+ public void stopAvrcpUpdates(BluetoothDevice device) {
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS)
.sendToTarget();
}
@@ -336,9 +382,12 @@
return null;
}
- if (!device.equals(mConnectedDevice)) {
+ if (!mStateMachines.containsKey(device)) {
return null;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return null;
return mAvrcpCtSm.getCurrentMetaData();
}
@@ -358,12 +407,15 @@
return null;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "Device " + device + " does not match connected devices");
return null;
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return null;
return mAvrcpCtSm.getCurrentPlayBackState(cached);
}
@@ -377,8 +429,8 @@
return null;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, " Device " + device + "does not match connected devices");
return null;
}
@@ -420,8 +472,8 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "getChildren device " + device + " does not match connected devices");
return false;
}
@@ -429,6 +481,9 @@
Log.e(TAG, "getChildren browse not yet connected");
return false;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
if (!mAvrcpCtSm.isConnected()) {
return false;
@@ -449,9 +504,9 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
+ if (!mStateMachines.containsKey(device)) {
Log.e(TAG,
- "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
+ "getNowPlayingList device " + device + " does not match connected devices");
return false;
}
@@ -461,7 +516,9 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
start, items, id);
@@ -481,8 +538,8 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "getFolderListing device " + device + " does not match Connected Devices");
return false;
}
@@ -492,7 +549,9 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
items, id);
@@ -511,8 +570,8 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "getPlayerList device " + device + " does not match Connected Devices");
return false;
}
@@ -522,7 +581,9 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start,
items);
@@ -542,8 +603,8 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
return false;
}
@@ -553,7 +614,9 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
Bundle b = new Bundle();
b.putString(EXTRA_FOLDER_ID, fid);
b.putString(EXTRA_FOLDER_BT_ID, uid);
@@ -574,8 +637,8 @@
return false;
}
- if (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
+ if (!mStateMachines.containsKey(device)) {
+ Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
return false;
}
@@ -585,7 +648,9 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return false;
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
0, fid);
@@ -603,9 +668,9 @@
return;
}
- if (!device.equals(mConnectedDevice)) {
+ if (!mStateMachines.containsKey(device)) {
Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
- + mConnectedDevice);
+ + "Connected Devices");
return;
}
@@ -613,9 +678,67 @@
Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
return;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
mAvrcpCtSm.fetchAttrAndPlayItem(uid);
}
+ // get state AvrcpControllerStateMachine corresponding to Bluetooth Device
+ public AvrcpControllerStateMachine getAvrcpCtStateMachine(BluetoothDevice device) {
+ if (device == null) {
+ return null;
+ }
+ AvrcpControllerStateMachine avrcpCtSm = mStateMachines.get(device);
+ if (avrcpCtSm == null) {
+ Log.d(TAG, "State Machine not found for device : " + device);
+ }
+ return avrcpCtSm;
+ }
+
+ // create state machine for new connection or return if already existing
+ public AvrcpControllerStateMachine getOrCreateAvrcpCtStateMachine(BluetoothDevice device) {
+ AvrcpControllerStateMachine avrcpCtSm = null;
+ synchronized (mStateMachines) {
+ avrcpCtSm = mStateMachines.get(device);
+ if (avrcpCtSm != null) {
+ Log.d(TAG, "State Machine is already present for device : " + device);
+ return avrcpCtSm;
+ }
+ if (mStateMachines.size() >= mMaxAllowedAvrcpConnection) {
+ Log.e(TAG, "Max Allowed AVRCP Connections already reached, Return.");
+ return null;
+ }
+ avrcpCtSm = new AvrcpControllerStateMachine(sAvrcpControllerService, device);
+ avrcpCtSm.start();
+ mStateMachines.put(device, avrcpCtSm);
+ }
+ return avrcpCtSm;
+ }
+
+ protected static void removeStateMachine(BluetoothDevice device) {
+ synchronized (mStateMachines) {
+ AvrcpControllerStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ Log.e(TAG, "State Machine already removed for device:" + device);
+ return;
+ }
+ Log.d(TAG, "State Machine removed for device:" + device);
+ mStateMachines.remove(device);
+ sm.doQuit();
+ sm = null;
+ }
+ }
+
+ // check if Browsing is connected for device
+ public boolean isBrowsingConnected(BluetoothDevice device) {
+ AvrcpControllerStateMachine avrcpCtSm = getAvrcpCtStateMachine(device);
+ if (avrcpCtSm == null) {
+ return false;
+ }
+ return avrcpCtSm.isBrowsingConnected();
+ }
+
//Binder object: Must be static class or memory leak may occur
private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
@@ -731,32 +854,30 @@
private synchronized void onConnectionStateChanged(boolean rcConnected, boolean brConnected,
byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Log.d(TAG, "onConnectionStateChanged " + rcConnected + " " + brConnected + device
- + " conn device " + mConnectedDevice);
+ Log.d(TAG, "onConnectionStateChanged: RC = " + rcConnected + ", BR = " + brConnected
+ + ", for: " + 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
+ int oldState = (mStateMachines.containsKey(device) ? BluetoothProfile.STATE_CONNECTED
: BluetoothProfile.STATE_DISCONNECTED);
int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
: BluetoothProfile.STATE_DISCONNECTED);
+ AvrcpControllerStateMachine mAvrcpCtSm = getOrCreateAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
+ }
+
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);
@@ -776,11 +897,14 @@
}
// Called by JNI to notify Avrcp of features supported by the Remote device.
- private void getRcFeatures(byte[] address, int features) {
+ private void getRcFeatures(byte[] address, int features, int caPsm) {
+ Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES,
- features, 0, device);
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, caPsm, device);
mAvrcpCtSm.sendMessage(msg);
}
@@ -793,10 +917,13 @@
private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
Log.d(TAG, "handleRegisterNotificationAbsVol ");
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
return;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
(int) label, 0);
@@ -807,10 +934,13 @@
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)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "handleSetAbsVolume device not found " + address);
return;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
mAvrcpCtSm.sendMessage(msg);
@@ -823,7 +953,7 @@
Log.d(TAG, "onTrackChanged");
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "onTrackChanged device not found " + address);
return;
}
@@ -837,11 +967,25 @@
if (VDBG) {
Log.d(TAG, "onTrackChanged " + trackInfo);
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
mAvrcpCtSm.sendMessage(msg);
}
+ private void onElementAttributeUpdate(byte[] address, byte numAttributes, int[] attributes,
+ String[] attribVals) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
+ CoverArtUtils coverArtUtils= new CoverArtUtils();
+ coverArtUtils.onElementAttributeUpdate(address, numAttributes, attributes, attribVals,
+ device, mAvrcpCtSm);
+ }
+
// Called by JNI periodically based upon timer to update play position
private synchronized void onPlayPositionChanged(byte[] address, int songLen,
int currSongPosition) {
@@ -849,10 +993,13 @@
Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
return;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
songLen, currSongPosition);
@@ -865,7 +1012,7 @@
Log.d(TAG, "onPlayStatusChanged " + playStatus);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
return;
}
@@ -889,6 +1036,9 @@
default:
playbackState = PlaybackState.STATE_NONE;
}
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null)
+ return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
mAvrcpCtSm.sendMessage(msg);
@@ -901,7 +1051,7 @@
Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
+ if (device != null && !mStateMachines.containsKey(device)) {
Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
return;
}
@@ -916,8 +1066,8 @@
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);
+ if (device != null && !mStateMachines.containsKey(device)) {
+ Log.e(TAG, "onPlayerAppSettingChanged: device not found " + address);
return;
}
PlayerApplicationSettings desiredSettings =
@@ -926,12 +1076,19 @@
}
// Browsing related JNI callbacks.
- void handleGetFolderItemsRsp(int status, MediaItem[] items) {
+ void handleGetFolderItemsRsp(int status, MediaItem[] items, byte[] address) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
+ items.length + " items.");
}
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ Log.d(TAG, "handleGetFolderItemsRsp device:" + device);
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
+ }
+
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
@@ -956,9 +1113,15 @@
mAvrcpCtSm.sendMessage(msg);
}
- void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
- if (DBG) {
- Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+ void handleGetPlayerItemsRsp(AvrcpPlayer[] items, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ if (DBG) {
+ Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length
+ + " items. Device: " + device);
+ }
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
}
for (AvrcpPlayer item : items) {
if (VDBG) {
@@ -1035,9 +1198,14 @@
return player;
}
- private void handleChangeFolderRsp(int count) {
+ private void handleChangeFolderRsp(int count, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
if (DBG) {
- Log.d(TAG, "handleChangeFolderRsp count: " + count);
+ Log.d(TAG, "handleChangeFolderRsp count: " + count + ", device: " + device);
+ }
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
}
Message msg =
mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
@@ -1045,18 +1213,28 @@
mAvrcpCtSm.sendMessage(msg);
}
- private void handleSetBrowsedPlayerRsp(int items, int depth) {
+ private void handleSetBrowsedPlayerRsp(int items, int depth, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
if (DBG) {
- Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
+ Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth + ", device: " + device);
+ }
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
}
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
mAvrcpCtSm.sendMessage(msg);
}
- private void handleSetAddressedPlayerRsp(int status) {
+ private void handleSetAddressedPlayerRsp(int status, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
if (DBG) {
- Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
+ Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status + ", device :" + device);
+ }
+ AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
+ if (mAvrcpCtSm == null) {
+ return;
}
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
@@ -1066,7 +1244,8 @@
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- mAvrcpCtSm.dump(sb);
+ for (AvrcpControllerStateMachine mAvrcpCtSm: mStateMachines.values())
+ mAvrcpCtSm.dump(sb);
}
public static String byteUIDToHexString(byte[] uid) {
@@ -1136,4 +1315,8 @@
static native void setBrowsedPlayerNative(byte[] address, int playerId);
static native void setAddressedPlayerNative(byte[] address, int playerId);
+
+ /* This api is used to fetch ElementAttributes */
+ native static void getElementAttributesNative(byte[] address, byte numAttributes,
+ byte[] attribIds);
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 3077664..0ac58d5 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -110,11 +110,12 @@
private static final String TAG = "AvrcpControllerSM";
private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean VDBG = AvrcpControllerService.VDBG;
private final Context mContext;
private final AudioManager mAudioManager;
-
+ private AvrcpControllerBipStateMachine mBipStateMachine;
+ private static CoverArtUtils mCoveArtUtils;
private final State mDisconnected;
private final State mConnected;
private final SetBrowsedPlayer mSetBrowsedPlayer;
@@ -143,9 +144,14 @@
// Browse tree.
private BrowseTree mBrowseTree = new BrowseTree();
- AvrcpControllerStateMachine(Context context) {
+ private final BluetoothDevice mDevice;
+ protected boolean mBrowsingConnected = false;
+ protected int prevState = -1;
+
+ AvrcpControllerStateMachine(Context context, BluetoothDevice device) {
super(TAG);
mContext = context;
+ mDevice = device;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
@@ -175,15 +181,26 @@
addState(mGetFolderList, mConnected);
addState(mGetPlayerListing, mConnected);
addState(mMoveToRoot, mConnected);
-
+ mCoveArtUtils = new CoverArtUtils();
setInitialState(mDisconnected);
+ mBipStateMachine = AvrcpControllerBipStateMachine.make(this, getHandler(), context);
}
class Disconnected extends State {
@Override
+ public void enter() {
+ Log.d(TAG, "Enter State: Disconnected mDevice: " + mDevice);
+ mBrowsingConnected = false;
+ if (prevState != -1) {
+ AvrcpControllerService.removeStateMachine(mDevice);
+ }
+ }
+
+ @Override
public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+ if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)
+ + ", mDevice: " + mDevice);
switch (msg.what) {
case MESSAGE_PROCESS_CONNECTION_CHANGE:
if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
@@ -215,6 +232,12 @@
}
return true;
}
+
+ @Override
+ public void exit() {
+ prevState = BluetoothProfile.STATE_DISCONNECTED;
+ log("Exit State: Disconnected: ");
+ }
}
class Connected extends State {
@@ -331,6 +354,7 @@
case MESSAGE_PROCESS_CONNECTION_CHANGE:
if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
+ mCoveArtUtils.msgDisconnectBip(mBipStateMachine,mRemoteDevice);
synchronized (mLock) {
mIsConnected = false;
mRemoteDevice = null;
@@ -363,12 +387,14 @@
if (msg.arg1 == 1) {
intent.putExtra(BluetoothProfile.EXTRA_STATE,
BluetoothProfile.STATE_CONNECTED);
+ mBrowsingConnected = true;
} else if (msg.arg1 == 0) {
intent.putExtra(BluetoothProfile.EXTRA_STATE,
BluetoothProfile.STATE_DISCONNECTED);
// If browse is disconnected, the next time we connect we should
// be at the ROOT.
mBrowseDepth = 0;
+ mBrowsingConnected = false;
} else {
Log.w(TAG, "Incorrect browse state " + msg.arg1);
}
@@ -378,6 +404,10 @@
case MESSAGE_PROCESS_RC_FEATURES:
mRemoteDevice.setRemoteFeatures(msg.arg1);
+ if (msg.arg2 > 0) {
+ mCoveArtUtils.msgProcessRcFeatures
+ (mBipStateMachine, mRemoteDevice,msg.arg2);
+ }
break;
case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
@@ -435,8 +465,11 @@
case MESSAGE_PROCESS_TRACK_CHANGED:
// Music start playing automatically and update Metadata
mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
- broadcastMetaDataChanged(
- mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+ if (mCoveArtUtils.msgTrackChanged(mContext, mBipStateMachine,
+ mAddressedPlayer,mRemoteDevice)) {
+ broadcastMetaDataChanged(
+ mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+ }
break;
case MESSAGE_PROCESS_PLAY_POS_CHANGED:
@@ -458,12 +491,25 @@
}
break;
+ case CoverArtUtils.MESSAGE_BIP_CONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
+ case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
+ mCoveArtUtils.processBipAction(mContext, mAddressedPlayer,
+ mRemoteDevice, msg.what, msg);
+ break;
default:
return false;
}
}
return true;
}
+
+ @Override
+ public void exit() {
+ prevState = BluetoothProfile.STATE_CONNECTED;
+ log("Exit State: Connected: " + mDevice + ", currentMsg: " + getCurrentMessage().what);
+ }
}
// Handle the change folder path meta-action.
@@ -995,7 +1041,9 @@
// If the receiver was never registered unregister will throw an
// IllegalArgumentException.
}
- quit();
+ mCoveArtUtils.closeBip(mBipStateMachine);
+ // we should disacrd, all currently queuedup messages.
+ quitNow();
}
void dump(StringBuilder sb) {
@@ -1175,6 +1223,19 @@
private void setAbsVolume(int absVol, int label) {
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ /* If SetAbsVolume Control Cmd is received from non-Streaming device then the
+ * requested volume level will not be set fot rendering and current Abs vol level
+ * at DUT (sink: rendering device) will be sent in response. */
+ Log.d(TAG, "Streaming device: " + A2dpSinkService.getCurrentStreamingDevice()
+ + " Device:" + mDevice);
+ if (!mDevice.equals(A2dpSinkService.getCurrentStreamingDevice())) {
+ absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
+ Log.w(TAG, "Volume change request came from non-streaming device," +
+ "respond with current absVol: " + absVol);
+ AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
+ label);
+ return;
+ }
// Ignore first volume command since phone may not know difference between stream volume
// and amplifier volume.
if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
@@ -1251,6 +1312,12 @@
case MESSAGE_PROCESS_CONNECTION_CHANGE:
str = "CB_CONN_CHANGED";
break;
+ case CoverArtUtils.MESSAGE_BIP_CONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
+ case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
+ str = mCoveArtUtils.dumpMessageString(message);
+ break;
default:
str = Integer.toString(message);
break;
@@ -1286,4 +1353,13 @@
}
return sb.toString();
}
+
+ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ boolean isBrowsingConnected() {
+ Log.d(TAG, "mBrowsingConnected = " + mBrowsingConnected);
+ return mBrowsingConnected;
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4482bd7..46addcf 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -41,7 +41,7 @@
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 VDBG = AvrcpControllerService.VDBG;
public static final int DIRECTION_DOWN = 0;
public static final int DIRECTION_UP = 1;
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
index 7946747..07c2a4b 100644
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
@@ -33,14 +33,17 @@
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 FEAT_COVER_ART = 8;
private static final int VOLUME_LABEL_UNDEFINED = -1;
+ private static final int L2CAP_PSM_UNDEFINED = -1;
final BluetoothDevice mBTDevice;
private int mRemoteFeatures;
private boolean mAbsVolNotificationRequested;
private boolean mFirstAbsVolCmdRecvd;
private int mNotificationLabel;
+ private int mBipL2capPsm;
RemoteDevice(BluetoothDevice mDevice) {
mBTDevice = mDevice;
@@ -48,13 +51,34 @@
mAbsVolNotificationRequested = false;
mNotificationLabel = VOLUME_LABEL_UNDEFINED;
mFirstAbsVolCmdRecvd = false;
+ mBipL2capPsm = L2CAP_PSM_UNDEFINED;
}
synchronized void setRemoteFeatures(int remoteFeatures) {
mRemoteFeatures = remoteFeatures;
}
- public synchronized byte[] getBluetoothAddress() {
+ synchronized void setRemoteBipPsm( int remotePsm) {
+ mBipL2capPsm = remotePsm;
+ }
+
+ synchronized int getRemoteBipPsm () {
+ return mBipL2capPsm;
+ }
+
+ synchronized boolean isBrowsingSupported() {
+ return ((mRemoteFeatures & FEAT_BROWSE) != 0);
+ }
+
+ synchronized boolean isMetaDataSupported() {
+ return ((mRemoteFeatures & FEAT_METADATA) != 0);
+ }
+
+ synchronized boolean isCoverArtSupported() {
+ return ((mRemoteFeatures & FEAT_COVER_ART) != 0);
+ }
+
+ synchronized public byte[] getBluetoothAddress() {
return Utils.getByteAddress(mBTDevice);
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index e89fb4c..f474495 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -17,7 +17,10 @@
package com.android.bluetooth.avrcpcontroller;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.media.MediaMetadata;
+import android.net.Uri;
import android.util.Log;
import java.util.ArrayList;
@@ -32,7 +35,7 @@
*/
class TrackInfo {
private static final String TAG = "AvrcpTrackInfo";
- private static final boolean VDBG = false;
+ private static final boolean VDBG = AvrcpControllerService.VDBG;
/*
* Default values for each of the items from JNI
@@ -52,6 +55,7 @@
private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+ private static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
private final String mArtistName;
@@ -61,6 +65,9 @@
private final long mTrackNum; // number of audio file on original recording.
private final long mTotalTracks; // total number of tracks on original recording
private final long mTrackLen; // full length of AudioFile.
+ private String mCoverArtHandle;
+ private String mImageLocation;
+ private String mThumbNailLocation;
TrackInfo() {
this(new ArrayList<Integer>(), new ArrayList<String>());
@@ -92,14 +99,36 @@
attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
: TOTAL_TRACK_TIME_INVALID;
+ mCoverArtHandle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_COVER_ART_HANDLE,
+ UNPOPULATED_ATTRIBUTE);
+ mImageLocation = UNPOPULATED_ATTRIBUTE;
+ mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
}
- @Override
+ boolean updateImageLocation(String mCAHandle, String mLocation) {
+ if (VDBG) Log.d(TAG, " updateImageLocation hndl " + mCAHandle + " location " + mLocation);
+ if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+ return false;
+ }
+ mImageLocation = mLocation;
+ return true;
+ }
+
+ boolean updateThumbNailLocation(String mCAHandle, String mLocation) {
+ if (VDBG) Log.d(TAG, " mCAHandle " + mCAHandle + " location " + mLocation);
+ if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+ return false;
+ }
+ mThumbNailLocation = mLocation;
+ return true;
+ }
+
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) + "]";
+ mTotalTracks) + " mCoverArtHandle=" + mCoverArtHandle +
+ " mImageLocation :"+mImageLocation+"]";
}
public MediaMetadata getMediaMetaData() {
@@ -114,6 +143,16 @@
mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, mTrackNum);
mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, mTotalTracks);
mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mTrackLen);
+ if (mImageLocation != UNPOPULATED_ATTRIBUTE) {
+ Uri imageUri = Uri.parse(mImageLocation);
+ if (VDBG) Log.d(TAG," updating image uri = " + imageUri.toString());
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ imageUri.toString());
+ }
+ if (mThumbNailLocation != UNPOPULATED_ATTRIBUTE) {
+ Bitmap mThumbNailBitmap = BitmapFactory.decodeFile(mThumbNailLocation);
+ mMetaDataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mThumbNailBitmap);
+ }
return mMetaDataBuilder.build();
}
@@ -144,4 +183,15 @@
}
return sb.toString();
}
+
+ String getCoverArtHandle() {
+ return mCoverArtHandle;
+ }
+
+ void clearCoverArtData() {
+ mCoverArtHandle = UNPOPULATED_ATTRIBUTE;
+ mImageLocation = UNPOPULATED_ATTRIBUTE;
+ mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
+ }
+
}
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 6a2e636..597dc45 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -1,4 +1,39 @@
/*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -82,4 +117,26 @@
public static final int BT_STATUS_RMT_DEV_DOWN = 10;
public static final int BT_STATUS_AUTH_REJECTED = 11;
public static final int BT_STATUS_AUTH_TIMEOUT = 12;
+
+ // Profile IDs to get profile features from profile_conf
+ public static final int AVRCP = 1;
+ public static final int PBAP = 2;
+ public static final int MAP = 3;
+
+ // Profile features supported in profile_conf
+ public static final int PROFILE_VERSION =1;
+ public static final int AVRCP_COVERART_SUPPORT = 2;
+ public static final int AVRCP_0103_SUPPORT = 3;
+ public static final int USE_SIM_SUPPORT = 4;
+ public static final int MAP_EMAIL_SUPPORT = 5;
+ public static final int PBAP_0102_SUPPORT = 6;
+
+ static final int BT_VENDOR_PROPERTY_TWS_PLUS_DEVICE_TYPE = 0x01;
+ static final int BT_VENDOR_PROPERTY_TWS_PLUS_PEER_ADDR = 0x02;
+ static final int BT_VENDOR_PROPERTY_TWS_PLUS_AUTO_CONNECT = 0x03;
+ static final int BT_VENDOR_PROPERTY_HOST_ADD_ON_FEATURES = 0x04;
+ static final int BT_VENDOR_PROPERTY_SOC_ADD_ON_FEATURES = 0x05;
+ static final int TWS_PLUS_DEV_TYPE_NONE = 0x00;
+ static final int TWS_PLUS_DEV_TYPE_PRIMARY = 0x01;
+ static final int TWS_PLUS_DEV_TYPE_SECONDARY = 0x02;
}
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index e1f999d..ea414c5 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -39,6 +39,7 @@
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.ba.BATService;
import java.util.LinkedList;
import java.util.List;
@@ -190,6 +191,10 @@
Intent intent = (Intent) msg.obj;
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device.getAddress().equals(BATService.mBAAddress)) {
+ Log.d(TAG," Update from BA, bail out");
+ break;
+ }
int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
if (prevState == nextState) {
@@ -222,8 +227,18 @@
+ "device " + device + " disconnected");
}
mA2dpConnectedDevices.remove(device);
+ final A2dpService a2dpService = mFactory.getA2dpService();
+
if (Objects.equals(mA2dpActiveDevice, device)) {
- setA2dpActiveDevice(null);
+ if (!mA2dpConnectedDevices.isEmpty() &&
+ mAdapterService.isTwsPlusDevice(mA2dpConnectedDevices.get(0)) &&
+ (a2dpService != null) &&
+ (a2dpService.getConnectionState(mA2dpConnectedDevices.get(0)) ==
+ BluetoothProfile.STATE_CONNECTED)) {
+ Log.d(TAG, "calling set a2dp Active dev: " + mA2dpConnectedDevices.get(0));
+ setA2dpActiveDevice(mA2dpConnectedDevices.get(0));
+ } else
+ setA2dpActiveDevice(null);
}
}
}
@@ -280,9 +295,20 @@
"handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
+ "device " + device + " disconnected");
}
+ final HeadsetService hfpService = mFactory.getHeadsetService();
+
mHfpConnectedDevices.remove(device);
if (Objects.equals(mHfpActiveDevice, device)) {
- setHfpActiveDevice(null);
+ if (!mHfpConnectedDevices.isEmpty() &&
+ mAdapterService.isTwsPlusDevice(mHfpConnectedDevices.get(0)) &&
+ (hfpService != null) &&
+ (hfpService.getConnectionState(mHfpConnectedDevices.get(0)) ==
+ BluetoothProfile.STATE_CONNECTED)) {
+ setHfpActiveDevice(mHfpConnectedDevices.get(0));
+ Log.d(TAG, "calling set Active dev: " + mHfpConnectedDevices.get(0));
+ } else {
+ setHfpActiveDevice(null);
+ }
}
}
}
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index d594850..a89aeef 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -62,10 +62,14 @@
"persist.bluetooth.maxconnectedaudiodevices";
static final int MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND = 1;
private static final int MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND = 5;
- private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY =
- "ro.bluetooth.a2dp_offload.supported";
+ private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
+
+ private static final String A2DP_OFFLOAD_ENABLE_PROPERTY =
+ "persist.bluetooth.a2dp_offload.enable";
private static final String A2DP_OFFLOAD_DISABLED_PROPERTY =
"persist.bluetooth.a2dp_offload.disabled";
+ private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY =
+ "ro.bluetooth.a2dp_offload.supported";
private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800;
private static final int BD_ADDR_LEN = 6; // in bytes
@@ -109,6 +113,36 @@
private boolean mIsLeExtendedAdvertisingSupported;
private boolean mIsLePeriodicAdvertisingSupported;
private int mLeMaximumAdvertisingDataLength;
+ private boolean mWiPowerFastbootEnabled;
+ private boolean mSplitA2DPScrambleDataRequired;
+ private boolean mSplitA2DP44p1KhzSampleFreq;
+ private boolean mSplitA2DP48KhzSampleFreq;
+ private boolean mSplitA2DPSingleVSCommandSupport;
+ private boolean mSplitA2DPSourceSBCEncoding;
+ private boolean mSplitA2DPSourceSBC;
+ private boolean mSplitA2DPSourceMP3;
+ private boolean mSplitA2DPSourceAAC;
+ private boolean mSplitA2DPSourceLDAC;
+ private boolean mSplitA2DPSourceAPTX;
+ private boolean mSplitA2DPSourceAPTXHD;
+ private boolean mSplitA2DPSourceAPTXADAPTIVE;
+ private boolean mSplitA2DPSourceAPTXTWSPLUS;
+ private boolean mSplitA2DPSinkSBC;
+ private boolean mSplitA2DPSinkMP3;
+ private boolean mSplitA2DPSinkAAC;
+ private boolean mSplitA2DPSinkLDAC;
+ private boolean mSplitA2DPSinkAPTX;
+ private boolean mSplitA2DPSinkAPTXHD;
+ private boolean mSplitA2DPSinkAPTXADAPTIVE;
+ private boolean mSplitA2DPSinkAPTXTWSPLUS;
+ private boolean mVoiceDualSCO;
+ private boolean mVoiceTWSPLUSeSCOAG;
+ private boolean mSWBVoicewithAptxAdaptiveAG;
+ private boolean mBroadcastAudioTxwithEC_2_5;
+ private boolean mBroadcastAudioTxwithEC_3_9;
+ private boolean mBroadcastAudioRxwithEC_2_5;
+ private boolean mBroadcastAudioRxwithEC_3_9;
+ private boolean mAddonFeaturesSupported;
private boolean mReceiverRegistered;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -190,6 +224,12 @@
// Make sure the final value of max connected audio devices is within allowed range
mMaxConnectedAudioDevices = Math.min(Math.max(propertyOverlayedMaxConnectedAudioDevices,
MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND), MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND);
+ // if QTI stack, overwrite max audio connections to 2
+ if(mService.isVendorIntfEnabled() && mMaxConnectedAudioDevices > 2) {
+ Log.i(TAG, "overwriting mMaxConnectedAudioDevices to 2 for vendor stack");
+ mMaxConnectedAudioDevices = 2;
+ }
+
Log.i(TAG, "init(), maxConnectedAudioDevices, default="
+ configDefaultMaxConnectedAudioDevices + ", propertyOverlayed="
+ propertyOverlayedMaxConnectedAudioDevices + ", finalValue="
@@ -220,7 +260,9 @@
mRemoteDevices = null;
mProfileConnectionState.clear();
if (mReceiverRegistered) {
- mService.unregisterReceiver(mReceiver);
+ if (mReceiver != null) {
+ mService.unregisterReceiver(mReceiver);
+ }
mReceiverRegistered = false;
}
mService = null;
@@ -245,6 +287,9 @@
*/
boolean setName(String name) {
synchronized (mObject) {
+ if (name.length() > BLUETOOTH_NAME_MAX_LENGTH_BYTES) {
+ name = name.substring(0, BLUETOOTH_NAME_MAX_LENGTH_BYTES);
+ }
return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME,
name.getBytes());
}
@@ -447,6 +492,215 @@
}
/**
+ * @return Wipower Fastboot status
+ */
+ boolean isWipowerFastbootEnabled() {
+ return mWiPowerFastbootEnabled;
+ }
+
+ /**
+ * @return Split A2DP Scramble Data Support status
+ */
+ boolean isSplitA2DPScrambleDataRequired() {
+ return mSplitA2DPScrambleDataRequired;
+ }
+
+ /**
+ * @return Split A2DP 44.1Khz Sample Freq status
+ */
+ boolean isSplitA2DP44p1KhzSampleFreq() {
+ return mSplitA2DP44p1KhzSampleFreq;
+ }
+
+ /**
+ * @return Split A2DP 48Khz Sample Freq status
+ */
+ boolean isSplitA2DP48KhzSampleFreq() {
+ return mSplitA2DP48KhzSampleFreq;
+ }
+ /**
+ * @return Split A2DP Single VS Command Support status
+ */
+ boolean isSplitA2DPSingleVSCommandSupport() {
+ return mSplitA2DPSingleVSCommandSupport;
+ }
+
+ /**
+ * @return Split A2DP Source SBC Encoding status
+ */
+ boolean isSplitA2DPSourceSBCEncoding() {
+ return mSplitA2DPSourceSBCEncoding;
+ }
+
+ /**
+ * @return Split A2DP Source SBC status
+ */
+ boolean isSplitA2DPSourceSBC() {
+ return mSplitA2DPSourceSBC;
+ }
+
+ /**
+ * @return Split A2DP Source MP3 status
+ */
+ boolean isSplitA2DPSourceMP3() {
+ return mSplitA2DPSourceMP3;
+ }
+
+ /**
+ * @return Split A2DP Source AAC status
+ */
+ boolean isSplitA2DPSourceAAC() {
+ return mSplitA2DPSourceAAC;
+ }
+
+ /**
+ * @return Split A2DP Source LDAC status
+ */
+ boolean isSplitA2DPSourceLDAC() {
+ return mSplitA2DPSourceLDAC;
+ }
+
+ /**
+ * @return Split A2DP Source APTX status
+ */
+ boolean isSplitA2DPSourceAPTX() {
+ return mSplitA2DPSourceAPTX;
+ }
+
+ /**
+ * @return Split A2DP Source APTXHD status
+ */
+ boolean isSplitA2DPSourceAPTXHD() {
+ return mSplitA2DPSourceAPTXHD;
+ }
+
+ /**
+ * @return Split A2DP Source APTXADAPTIVE status
+ */
+ boolean isSplitA2DPSourceAPTXADAPTIVE() {
+ return mSplitA2DPSourceAPTXADAPTIVE;
+ }
+
+ /**
+ * @return Split A2DP Source APTXTWSPLUS status
+ */
+ boolean isSplitA2DPSourceAPTXTWSPLUS() {
+ return mSplitA2DPSourceAPTXTWSPLUS;
+ }
+
+ /**
+ * @return Split A2DP Sink SBC status
+ */
+ boolean isSplitA2DPSinkSBC() {
+ return mSplitA2DPSinkSBC;
+ }
+
+ /**
+ * @return Split A2DP Sink MP3 status
+ */
+ boolean isSplitA2DPSinkMP3() {
+ return mSplitA2DPSinkMP3;
+ }
+
+ /**
+ * @return Split A2DP Sink AAC status
+ */
+ boolean isSplitA2DPSinkAAC() {
+ return mSplitA2DPSinkAAC;
+ }
+
+ /**
+ * @return Split A2DP Sink LDAC status
+ */
+ boolean isSplitA2DPSinkLDAC() {
+ return mSplitA2DPSinkLDAC;
+ }
+
+ /**
+ * @return Split A2DP Sink APTX status
+ */
+ boolean isSplitA2DPSinkAPTX() {
+ return mSplitA2DPSinkAPTX;
+ }
+
+ /**
+ * @return Split A2DP Sink APTXHD status
+ */
+ boolean isSplitA2DPSinkAPTXHD() {
+ return mSplitA2DPSinkAPTXHD;
+ }
+
+ /**
+ * @return Split A2DP Sink APTXADAPTIVE status
+ */
+ boolean isSplitA2DPSinkAPTXADAPTIVE() {
+ return mSplitA2DPSinkAPTXADAPTIVE;
+ }
+
+ /**
+ * @return Split A2DP Sink APTXTWSPLUS status
+ */
+ boolean isSplitA2DPSinkAPTXTWSPLUS() {
+ return mSplitA2DPSinkAPTXTWSPLUS;
+ }
+
+ /**
+ * @return mVoiceDualSCO status
+ */
+ boolean isVoiceDualSCO() {
+ return mVoiceDualSCO;
+ }
+
+ /**
+ * @return Voice TWS+ eSCO AG status
+ */
+ boolean isVoiceTWSPLUSeSCOAG() {
+ return mVoiceTWSPLUSeSCOAG;
+ }
+
+ /**
+ * @return SWB Voice with Aptx Adaptive AG status
+ */
+ boolean isSWBVoicewithAptxAdaptiveAG() {
+ return mSWBVoicewithAptxAdaptiveAG;
+ }
+
+ /**
+ * @return Broadcast Audio Tx with EC_2_5 status
+ */
+ boolean isBroadcastAudioTxwithEC_2_5() {
+ return mBroadcastAudioTxwithEC_2_5;
+ }
+
+ /**
+ * @return Broadcast Audio Tx with EC_3_9 status
+ */
+ boolean isBroadcastAudioTxwithEC_3_9() {
+ return mBroadcastAudioTxwithEC_3_9;
+ }
+
+ /**
+ * @return Broadcast Audio Rx with EC_2_5 status
+ */
+ boolean isBroadcastAudioRxwithEC_2_5() {
+ return mBroadcastAudioRxwithEC_2_5;
+ }
+
+ /**
+ * @return Broadcast Audio Rx with EC_3_9 status
+ */
+ boolean isBroadcastAudioRxwithEC_3_9() {
+ return mBroadcastAudioRxwithEC_3_9;
+ }
+
+ /**
+ * @return Broadcast AddonFeatures Cmd Support status
+ */
+ boolean isAddonFeaturesCmdSupported() {
+ return mAddonFeaturesSupported;
+ }
+
+ /**
* @return the mBondedDevices
*/
BluetoothDevice[] getBondedDevices() {
@@ -555,10 +809,22 @@
return;
}
+
synchronized (mObject) {
+
updateProfileConnectionState(profile, state, prevState);
- if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
+ boolean validateConnectionState = false;
+
+ try {
+ validateConnectionState =
+ updateCountersAndCheckForConnectionStateChange(state, prevState);
+ } catch (IllegalStateException ee) {
+ Log.w(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
+ + profile + ", " + prevState + " -> " + state);
+ }
+
+ if (validateConnectionState) {
int newAdapterState = convertToAdapterState(state);
int prevAdapterState = convertToAdapterState(prevState);
setConnectionState(newAdapterState);
@@ -834,6 +1100,78 @@
+ " mLeMaximumAdvertisingDataLength = " + mLeMaximumAdvertisingDataLength);
}
+ public void updateSocFeatureSupport(byte[] val) {
+ mAddonFeaturesSupported = (val.length != 0);
+ if (!mAddonFeaturesSupported) {
+ Log.d(TAG, "BT_PROPERTY_ADD_ON_FEATURES: add-on features VSC is not supported");
+ } else {
+ mWiPowerFastbootEnabled = ((0x01 & ((int) val[0])) != 0);
+ mSplitA2DPScrambleDataRequired = ((0x02 & ((int) val[0])) != 0);
+ mSplitA2DP44p1KhzSampleFreq = ((0x04 & ((int) val[0])) != 0);
+ mSplitA2DP48KhzSampleFreq = ((0x08 & ((int) val[0])) != 0);
+ mSplitA2DPSingleVSCommandSupport = ((0x10 & ((int) val[0])) != 0);
+ mSplitA2DPSourceSBCEncoding = ((0x20 & ((int) val[0])) != 0);
+ mSplitA2DPSourceSBC = ((0x01 & ((int) val[1])) != 0);
+ mSplitA2DPSourceMP3 = ((0x02 & ((int) val[1])) != 0);
+ mSplitA2DPSourceAAC = ((0x04 & ((int) val[1])) != 0);
+ mSplitA2DPSourceLDAC = ((0x08 & ((int) val[1])) != 0);
+ mSplitA2DPSourceAPTX = ((0x10 & ((int) val[1])) != 0);
+ mSplitA2DPSourceAPTXHD = ((0x20 & ((int) val[1])) != 0);
+ mSplitA2DPSourceAPTXADAPTIVE = ((0x40 & ((int) val[1])) != 0);
+ mSplitA2DPSourceAPTXTWSPLUS = ((0x80 & ((int) val[1])) != 0);
+ mSplitA2DPSinkSBC = ((0x01 & ((int) val[2])) != 0);
+ mSplitA2DPSinkMP3 = ((0x02 & ((int) val[2])) != 0);
+ mSplitA2DPSinkAAC = ((0x04 & ((int) val[2])) != 0);
+ mSplitA2DPSinkLDAC = ((0x08 & ((int) val[2])) != 0);
+ mSplitA2DPSinkAPTX = ((0x10 & ((int) val[2])) != 0);
+ mSplitA2DPSinkAPTXHD = ((0x20 & ((int) val[2])) != 0);
+ mSplitA2DPSinkAPTXADAPTIVE = ((0x40 & ((int) val[2])) != 0);
+ mSplitA2DPSinkAPTXTWSPLUS = ((0x80 & ((int) val[2])) != 0);
+ mVoiceDualSCO = ((0x01 & ((int) val[3])) != 0);
+ mVoiceTWSPLUSeSCOAG = ((0x02 & ((int) val[3])) != 0);
+ mSWBVoicewithAptxAdaptiveAG = ((0x04 & ((int) val[3])) != 0);
+ mBroadcastAudioTxwithEC_2_5 = ((0x01 & ((int) val[4])) != 0);
+ mBroadcastAudioTxwithEC_3_9 = ((0x02 & ((int) val[4])) != 0);
+ mBroadcastAudioRxwithEC_2_5 = ((0x04 & ((int) val[4])) != 0);
+ mBroadcastAudioRxwithEC_3_9 = ((0x08 & ((int) val[4])) != 0);
+
+
+ Log.d(TAG, "BT_PROPERTY_ADD_ON_FEATURES: update from BT controller"
+ + "\n mWiPowerFastbootEnabled = "
+ + mWiPowerFastbootEnabled + "\n SplitA2DPScrambleDataRequired = "
+ + mSplitA2DPScrambleDataRequired + "\n mSplitA2DP44p1KhzSampleFreq = "
+ + mSplitA2DP44p1KhzSampleFreq + "\n mSplitA2DP48KhzSampleFreq = "
+ + mSplitA2DP48KhzSampleFreq + "\n mSplitA2DPSingleVSCommandSupport = "
+ + mSplitA2DPSingleVSCommandSupport + "\n mSplitA2DPSourceSBCEncoding = "
+ + mSplitA2DPSourceSBCEncoding + "\n mSplitA2DPSourceSBC = "
+ + mSplitA2DPSourceSBC + "\n mSplitA2DPSourceMP3 = "
+ + mSplitA2DPSourceMP3 + "\n mSplitA2DPSourceAAC = "
+ + mSplitA2DPSourceAAC + "\n mSplitA2DPSourceLDAC = " + mSplitA2DPSourceLDAC
+ + "\n mSplitA2DPSourceAPTX = " + mSplitA2DPSourceAPTX
+ + "\n mSplitA2DPSourceAPTXHD = " + mSplitA2DPSourceAPTXHD
+ + "\n mSplitA2DPSourceAPTXADAPTIVE = " + mSplitA2DPSourceAPTXADAPTIVE
+ + "\n mSplitA2DPSourceAPTXTWSPLUS = " + mSplitA2DPSourceAPTXTWSPLUS
+ + "\n mSplitA2DPSinkSBC = " + mSplitA2DPSinkSBC + "\n mSplitA2DPSinkMP3 = "
+ + mSplitA2DPSinkMP3 + "\n mSplitA2DPSinkAAC = "
+ + mSplitA2DPSinkAAC + "\n mSplitA2DPSinkLDAC = " + mSplitA2DPSinkLDAC
+ + "\n mSplitA2DPSinkAPTX = " + mSplitA2DPSinkAPTX
+ + "\n mSplitA2DPSinkAPTXHD = " + mSplitA2DPSinkAPTXHD
+ + "\n mSplitA2DPSinkAPTXADAPTIVE = " + mSplitA2DPSinkAPTXADAPTIVE
+ + "\n mSplitA2DPSinkAPTXTWSPLUS = " + mSplitA2DPSinkAPTXTWSPLUS
+ + "\n mVoiceDualSCO = " + mVoiceDualSCO + "\n mVoiceTWSPLUSeSCOAG = "
+ + mVoiceTWSPLUSeSCOAG + "\n mSWBVoicewithAptxAdaptiveAG = "
+ + mSWBVoicewithAptxAdaptiveAG + "\n BroadcastAudioTxwithEC_2_5 = "
+ + mBroadcastAudioTxwithEC_2_5 + "\n mBroadcastAudioTxwithEC_3_9 = "
+ + mBroadcastAudioTxwithEC_3_9 + "\n mBroadcastAudioRxwithEC_2_5 = "
+ + mBroadcastAudioRxwithEC_2_5 + "\n mBroadcastAudioRxwithEC_3_9= "
+ + mBroadcastAudioRxwithEC_3_9);
+ }
+ }
+
+ public void updateHostFeatureSupport(byte[] val) {
+ Log.d(TAG, " Host Features are not supported currently ");
+ }
+
void onBluetoothReady() {
debugLog("onBluetoothReady, state=" + BluetoothAdapter.nameForState(getState())
+ ", ScanMode=" + mScanMode);
@@ -872,7 +1210,7 @@
infoLog("Callback:discoveryStateChangeCallback with state:" + state);
synchronized (mObject) {
Intent intent;
- if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {
+ if ((state == AbstractionLayer.BT_DISCOVERY_STOPPED) && mDiscovering) {
mDiscovering = false;
mDiscoveryEndMs = System.currentTimeMillis();
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index ad78955..2b3db26 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -1,4 +1,39 @@
/*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -68,9 +103,13 @@
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.sdp.SdpManager;
+import com.android.bluetooth.ba.BATService;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import android.net.wifi.WifiManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -96,6 +135,7 @@
private long mRxTimeTotalMs;
private long mIdleTimeTotalMs;
private long mEnergyUsedTotalVoltAmpSecMicro;
+ private WifiManager mWifiManager;
private final SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
private final ArrayList<ProfileService> mRegisteredProfiles = new ArrayList<>();
@@ -130,6 +170,7 @@
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
static {
+ System.loadLibrary("bluetooth_jni");
classInitNative();
}
@@ -156,6 +197,8 @@
private AdapterProperties mAdapterProperties;
private AdapterState mAdapterStateMachine;
+ private Vendor mVendor;
+ private boolean mVendorAvailble;
private BondStateMachine mBondStateMachine;
private JniCallbacks mJniCallbacks;
private RemoteDevices mRemoteDevices;
@@ -178,10 +221,12 @@
private PowerManager.WakeLock mWakeLock;
private String mWakeLockName;
private UserManager mUserManager;
+ private static BluetoothAdapter mAdapter;
private ProfileObserver mProfileObserver;
private PhonePolicy mPhonePolicy;
private ActiveDeviceManager mActiveDeviceManager;
+ private VendorSocket mVendorSocket;
/**
* Register a {@link ProfileService} with AdapterService.
@@ -217,6 +262,40 @@
mHandler.sendMessage(m);
}
+ public boolean getProfileInfo(int profile_id , int profile_info) {
+ if (isVendorIntfEnabled()) {
+ return mVendor.getProfileInfo(profile_id, profile_info);
+ } else {
+ return false;
+ }
+ }
+
+ private void fetchWifiState() {
+ if (!isVendorIntfEnabled()) {
+ return;
+ }
+ ConnectivityManager connMgr =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ if (networkInfo.isConnected()) {
+ mVendor.setWifiState(true);
+ } else {
+ mVendor.setWifiState(false);
+ }
+ }
+
+ public void StartHCIClose() {
+ if (isVendorIntfEnabled()) {
+ mVendor.HCIClose();
+ }
+ }
+
+ public void voipNetworkWifiInfo(boolean isVoipStarted, boolean isNetworkWifi) {
+ Log.i(TAG, "In voipNetworkWifiInfo, isVoipStarted: " + isVoipStarted +
+ ", isNetworkWifi: " + isNetworkWifi);
+ mVendor.voipNetworkWifiInformation(isVoipStarted, isNetworkWifi);
+ }
+
private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED = 1;
private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2;
private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3;
@@ -271,13 +350,17 @@
}
mRunningProfiles.add(profile);
if (GattService.class.getSimpleName().equals(profile.getName())) {
+ Log.w(TAG,"onProfileServiceStateChange() - Gatt profile service started..");
enableNativeWithGuestFlag();
} else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
&& mRegisteredProfiles.size() == mRunningProfiles.size()) {
+ Log.w(TAG,"onProfileServiceStateChange() - All profile services started..");
mAdapterProperties.onBluetoothReady();
updateUuids();
setBluetoothClassFromConfig();
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+ //update wifi state to lower layers
+ fetchWifiState();
}
break;
case BluetoothAdapter.STATE_OFF:
@@ -293,9 +376,10 @@
// If only GATT is left, send BREDR_STOPPED.
if ((mRunningProfiles.size() == 1 && (GattService.class.getSimpleName()
.equals(mRunningProfiles.get(0).getName())))) {
+ Log.w(TAG,"onProfileServiceStateChange() - All profile services except gatt stopped..");
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
} else if (mRunningProfiles.size() == 0) {
- disableNative();
+ Log.w(TAG,"onProfileServiceStateChange() - All profile services stopped..");
mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
}
break;
@@ -370,12 +454,15 @@
public void onCreate() {
super.onCreate();
debugLog("onCreate()");
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
mRemoteDevices.init();
mBinder = new AdapterServiceBinder(this);
mAdapterProperties = new AdapterProperties(this);
- mAdapterStateMachine = AdapterState.make(this);
+ mVendor = new Vendor(this);
+ mAdapterStateMachine = AdapterState.make(this);
mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
+ mVendorSocket = new VendorSocket(this);
initNative();
mNativeAvailable = true;
mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
@@ -403,6 +490,7 @@
} else {
Log.i(TAG, "Phone policy disabled");
}
+ mBondStateMachine = BondStateMachine.make(mPowerManager, this, mAdapterProperties, mRemoteDevices);
mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
mActiveDeviceManager.start();
@@ -423,6 +511,9 @@
return null;
}
}.execute();
+ mVendor.init();
+ mVendorAvailble = mVendor.getQtiStackStatus();
+ mVendorSocket.init();
try {
int systemUiUid = getApplicationContext().getPackageManager().getPackageUidAsUser(
@@ -441,6 +532,18 @@
int fuid = ActivityManager.getCurrentUser();
Utils.setForegroundUserId(fuid);
setForegroundUserIdNative(fuid);
+
+ // Reset |mRemoteDevices| whenever BLE is turned off then on
+ // This is to replace the fact that |mRemoteDevices| was
+ // reinitialized in previous code.
+ //
+ // TODO(apanicke): The reason is unclear but
+ // I believe it is to clear the variable every time BLE was
+ // turned off then on. The same effect can be achieved by
+ // calling cleanup but this may not be necessary at all
+ // We should figure out why this is needed later
+ mRemoteDevices.reset();
+ mAdapterProperties.init(mRemoteDevices);
}
@Override
@@ -451,7 +554,7 @@
@Override
public boolean onUnbind(Intent intent) {
- debugLog("onUnbind() - calling cleanup");
+ Log.w(TAG, "onUnbind, calling cleanup");
cleanup();
return super.onUnbind(intent);
}
@@ -480,26 +583,16 @@
void bringUpBle() {
debugLog("bleOnProcessStart()");
-
+ /* To reload profile support in BLE turning ON state. So even if profile support
+ * is disabled in Bluetooth Adapter turned off state(10), this flag will ensure
+ * rechecking profile support after BT is turned ON
+ */
if (getResources().getBoolean(
- R.bool.config_bluetooth_reload_supported_profiles_when_enabled)) {
+ com.android.bluetooth.R.bool.reload_supported_profiles_when_enabled)) {
Config.init(getApplicationContext());
}
- // Reset |mRemoteDevices| whenever BLE is turned off then on
- // This is to replace the fact that |mRemoteDevices| was
- // reinitialized in previous code.
- //
- // TODO(apanicke): The reason is unclear but
- // I believe it is to clear the variable every time BLE was
- // turned off then on. The same effect can be achieved by
- // calling cleanup but this may not be necessary at all
- // We should figure out why this is needed later
- mRemoteDevices.reset();
- mAdapterProperties.init(mRemoteDevices);
-
- debugLog("bleOnProcessStart() - Make Bond State Machine");
- mBondStateMachine = BondStateMachine.make(this, mAdapterProperties, mRemoteDevices);
+ debugLog("BleOnProcessStart() - Make Bond State Machine");
mJniCallbacks.init(mBondStateMachine, mRemoteDevices);
@@ -522,6 +615,7 @@
void stateChangeCallback(int status) {
if (status == AbstractionLayer.BT_STATE_OFF) {
debugLog("stateChangeCallback: disableNative() completed");
+ mAdapterStateMachine.sendMessage(AdapterState.STACK_DISABLED);
} else if (status == AbstractionLayer.BT_STATE_ON) {
mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
} else {
@@ -529,6 +623,12 @@
}
}
+ void ssrCleanupCallback() {
+ disableProfileServices(false);
+ Log.e(TAG, "Killing the process to force restart as part of fault tolerance");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
/**
* Sets the Bluetooth CoD value of the local adapter if there exists a config value for it.
*/
@@ -555,6 +655,10 @@
return result;
}
+ void startBluetoothDisable() {
+ mAdapterStateMachine.sendMessage(AdapterState.BEGIN_BREDR_STOP);
+ }
+
void startProfileServices() {
debugLog("startCoreServices()");
Class[] supportedProfileServices = Config.getSupportedProfiles();
@@ -569,8 +673,17 @@
}
}
- void stopProfileServices() {
+ void startBrEdrCleanup(){
mAdapterProperties.onBluetoothDisable();
+ if (isVendorIntfEnabled()) {
+ mVendor.bredrCleanup();
+ } else {
+ mAdapterStateMachine.sendMessage(
+ mAdapterStateMachine.obtainMessage(AdapterState.BEGIN_BREDR_STOP));
+ }
+ }
+
+ void stopProfileServices() {
Class[] supportedProfileServices = Config.getSupportedProfiles();
if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
&& GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
@@ -581,6 +694,27 @@
}
}
+ void disableProfileServices(boolean onlyGatt) {
+ Class[] services = Config.getSupportedProfiles();
+ for (int i = 0; i < services.length; i++) {
+ if (onlyGatt && !(GattService.class.getSimpleName().equals(services[i].getSimpleName())))
+ continue;
+ boolean res = false;
+ String serviceName = services[i].getName();
+ mProfileServicesState.put(serviceName,BluetoothAdapter.STATE_OFF);
+ Intent intent = new Intent(this,services[i]);
+ intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ res = stopService(intent);
+ Log.d(TAG, "disableProfileServices() - Stopping service "
+ + serviceName + " with result: " + res);
+ if(onlyGatt)
+ break;
+ }
+ return;
+ }
+
private void stopGattProfileService() {
mAdapterProperties.onBleDisable();
if (mRunningProfiles.size() == 0) {
@@ -626,6 +760,12 @@
return;
}
+ // Unregistering Bluetooth Adapter
+ if ( mAdapter!= null ){
+ mAdapter.unregisterAdapter();
+ mAdapter = null;
+ }
+
clearAdapterService(this);
mCleaningUp = true;
@@ -675,6 +815,14 @@
mAdapterProperties.cleanup();
}
+ if (mVendor != null) {
+ mVendor.cleanup();
+ }
+
+ if (mVendorSocket!= null) {
+ mVendorSocket.cleanup();
+ }
+
if (mJniCallbacks != null) {
mJniCallbacks.cleanup();
}
@@ -705,6 +853,7 @@
Intent intent = new Intent(this, service);
intent.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
startService(intent);
}
@@ -1395,6 +1544,28 @@
return service.sdpSearch(device, uuid);
}
+ public boolean isTwsPlusDevice(BluetoothDevice device) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"(): isTws+device(): not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return false;
+ return service.isTwsPlusDevice(device);
+ }
+
+ public String getTwsPlusPeerAddress(BluetoothDevice device) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"getTws+peerAddress(): not allowed for non-active user");
+ return null;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return null;
+ return service.getTwsPlusPeerAddress(device);
+ }
+
@Override
public int getBatteryLevel(BluetoothDevice device) {
if (!Utils.checkCaller()) {
@@ -1435,9 +1606,13 @@
if (service == null) {
return false;
}
- service.disable();
+ if ((getState() == BluetoothAdapter.STATE_BLE_ON) ||
+ (getState() == BluetoothAdapter.STATE_BLE_TURNING_ON)) {
+ service.onBrEdrDown();
+ } else {
+ service.disable();
+ }
return service.factoryReset();
-
}
@Override
@@ -1567,6 +1742,16 @@
}
@Override
+ public void updateQuietModeStatus(boolean quietMode) {
+ AdapterService service = getService();
+ if (service == null) {
+ return;
+ }
+ service.updateQuietModeStatus(quietMode);
+ }
+
+
+ @Override
public void onBrEdrDown() {
AdapterService service = getService();
if (service == null) {
@@ -1575,6 +1760,29 @@
service.onBrEdrDown();
}
+ public int setSocketOpt(int type, int channel, int optionName, byte [] optionVal,
+ int optionLen) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"setSocketOpt(): not allowed for non-active user");
+ return -1;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return -1;
+ return service.setSocketOpt(type, channel, optionName, optionVal, optionLen);
+ }
+
+ public int getSocketOpt(int type, int channel, int optionName, byte [] optionVal) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"getSocketOpt(): not allowed for non-active user");
+ return -1;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return -1;
+ return service.getSocketOpt(type, channel, optionName, optionVal);
+ }
+
@Override
public void dump(FileDescriptor fd, String[] args) {
PrintWriter writer = new PrintWriter(new FileOutputStream(fd));
@@ -1596,6 +1804,10 @@
return mAdapterProperties.getState() == BluetoothAdapter.STATE_ON;
}
+ public boolean isVendorIntfEnabled() {
+ return mVendorAvailble;
+ }
+
public int getState() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mAdapterProperties != null) {
@@ -1690,6 +1902,74 @@
return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
}
+ boolean setTwsPlusDevType(byte[] address, short twsPlusDevType) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ BluetoothDevice device = mRemoteDevices.getDevice(address);
+ DeviceProperties deviceProp;
+ if (device == null) {
+ deviceProp = mRemoteDevices.addDeviceProperties(address);
+ } else {
+ deviceProp = mRemoteDevices.getDeviceProperties(device);
+ }
+ if(deviceProp != null) {
+ deviceProp.setTwsPlusDevType(twsPlusDevType);
+ return true;
+ }
+ return false;
+ }
+
+ boolean setTwsPlusPeerEbAddress(byte[] address, byte[] peerEbAddress) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ BluetoothDevice device = mRemoteDevices.getDevice(address);
+ BluetoothDevice peerDevice = null;
+ DeviceProperties deviceProp;
+ DeviceProperties peerDeviceProp;
+
+ if(peerEbAddress != null)
+ peerDevice = mRemoteDevices.getDevice(peerEbAddress);
+
+ if (device == null) {
+ deviceProp = mRemoteDevices.addDeviceProperties(address);
+ } else {
+ deviceProp = mRemoteDevices.getDeviceProperties(device);
+ }
+ if (peerDevice == null && peerEbAddress != null) {
+ peerDeviceProp = mRemoteDevices.addDeviceProperties(peerEbAddress);
+ peerDevice = mRemoteDevices.getDevice(peerEbAddress);
+ }
+ if(deviceProp != null) {
+ deviceProp.setTwsPlusPeerEbAddress(peerDevice, peerEbAddress);
+ return true;
+ }
+ return false;
+ }
+
+ boolean setTwsPlusAutoConnect(byte[] address, boolean autoConnect) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ BluetoothDevice device = mRemoteDevices.getDevice(address);
+ BluetoothDevice peerDevice = null;
+ DeviceProperties deviceProp;
+
+ if (device == null) {
+ deviceProp = mRemoteDevices.addDeviceProperties(address);
+ } else {
+ deviceProp = mRemoteDevices.getDeviceProperties(device);
+ }
+ if(deviceProp != null) {
+ deviceProp.setTwsPlusAutoConnect(peerDevice, autoConnect);
+ return true;
+ }
+ return false;
+ }
+
+ void updateHostFeatureSupport(byte[] val) {
+ mAdapterProperties.updateHostFeatureSupport(val);
+ }
+
+ void updateSocFeatureSupport(byte[] val) {
+ mAdapterProperties.updateSocFeatureSupport(val);
+ }
+
int getScanMode() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
@@ -1721,6 +2001,10 @@
debugLog("startDiscovery");
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+ if (mAdapterProperties.isDiscovering()) {
+ Log.i(TAG,"discovery already active, ignore startDiscovery");
+ return false;
+ }
return startDiscoveryNative();
}
@@ -1728,6 +2012,10 @@
debugLog("cancelDiscovery");
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+ if (!mAdapterProperties.isDiscovering()) {
+ Log.i(TAG,"discovery not active, ignore cancelDiscovery");
+ return false;
+ }
return cancelDiscoveryNative();
}
@@ -1774,6 +2062,35 @@
}
}
+ public boolean isTwsPlusDevice(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return false;
+ }
+ return (deviceProp.getTwsPlusPeerAddress() != null);
+ }
+
+ public String getTwsPlusPeerAddress(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return null;
+ }
+ byte[] address = deviceProp.getTwsPlusPeerAddress();
+ return Utils.getAddressStringFromByte(address);
+ }
+
+ public BluetoothDevice getTwsPlusPeerDevice(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return null;
+ }
+ byte[] address = deviceProp.getTwsPlusPeerAddress();
+ return mRemoteDevices.getDevice(address);
+ }
+
boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
@@ -1785,7 +2102,11 @@
// Pairing is unreliable while scanning, so cancel discovery
// Note, remove this when native stack improves
- cancelDiscoveryNative();
+ if (!mAdapterProperties.isDiscovering()) {
+ Log.i(TAG,"discovery not active, no need to send cancelDiscovery");
+ } else {
+ cancelDiscoveryNative();
+ }
Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
msg.obj = device;
@@ -1889,6 +2210,10 @@
*/
public String getRemoteName(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (device.getAddress().equals(BATService.mBAAddress)) {
+ Log.d(TAG," Request Name for BA device ");
+ return "Broadcast_Audio";
+ }
if (mRemoteDevices == null) {
return null;
}
@@ -1955,6 +2280,10 @@
boolean fetchRemoteUuids(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (device.getAddress().equals(BATService.mBAAddress)) {
+ Log.d(TAG," Update from BA, don't check UUIDS, bail out");
+ return false;
+ }
mRemoteDevices.fetchUuids(device);
return true;
}
@@ -2197,6 +2526,312 @@
return mAdapterProperties.isA2dpOffloadEnabled();
}
+ /**
+ * Check whether Wipower Fastboot enabled.
+ *
+ * @return true if Wipower fastboot is enabled
+ */
+ public boolean isWipowerFastbootEnabled() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isWipowerFastbootEnabled();
+ }
+
+ /**
+ * Check whether Split A2DP Scramble Data Required
+ *
+ * @return true if Split A2DP Scramble Data Required is enabled
+ */
+ public boolean isSplitA2DPScrambleDataRequired() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPScrambleDataRequired();
+ }
+
+ /**
+ * Check whether Split A2DP 44.1Khz Sample Freq enabled.
+ *
+ * @return true if Split A2DP 44.1Khz Sample Freq is enabled
+ */
+ public boolean isSplitA2DP44p1KhzSampleFreq() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DP44p1KhzSampleFreq();
+ }
+
+ /**
+ * Check whether Split A2DP 48Khz Sample Freq enabled.
+ *
+ * @return true if Split A2DP 48Khz Sample Freq is enabled
+ */
+ public boolean isSplitA2DP48KhzSampleFreq() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DP48KhzSampleFreq();
+ }
+
+ /**
+ * Check whether Split A2DP Single VS Command Support enabled.
+ *
+ * @return true if Split A2DP Single VSCommand Support is enabled
+ */
+ public boolean isSplitA2DPSingleVSCommandSupport() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSingleVSCommandSupport();
+ }
+
+ /**
+ * Check whether Split A2DP Source SBC Encoding enabled.
+ *
+ * @return true if Split A2DP Source SBC Encoding is enabled
+ */
+ public boolean isSplitA2DPSourceSBCEncoding() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceSBCEncoding();
+ }
+
+ /**
+ * Check whether Split A2DP Source SBC enabled.
+ *
+ * @return true if Split A2DP Source SBC is enabled
+ */
+ public boolean isSplitA2DPSourceSBC() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceSBC();
+ }
+
+ /**
+ * Check whether Split A2DP Source MP3 enabled.
+ *
+ * @return true if Split A2DP Source MP3 is enabled
+ */
+ public boolean isSplitA2DPSourceMP3() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceMP3();
+ }
+
+ /**
+ * Check whether Split A2DP Source AAC enabled.
+ *
+ * @return true if Split A2DP Source AAC is enabled
+ */
+ public boolean isSplitA2DPSourceAAC() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceAAC();
+ }
+
+ /**
+ * Check whether Split A2DP Source LDAC enabled.
+ *
+ * @return true if Split A2DP Source LDAC is enabled
+ */
+ public boolean isSplitA2DPSourceLDAC() {
+ String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceLDAC();
+ }
+
+ /**
+ * Check whether Split A2DP Source APTX enabled.
+ *
+ * @return true if Split A2DP Source APTX is enabled
+ */
+ public boolean isSplitA2DPSourceAPTX() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceAPTX();
+ }
+
+ /**
+ * Check whether Split A2DP Source APTX HD enabled.
+ *
+ * @return true if Split A2DP Source APTX HD is enabled
+ */
+ public boolean isSplitA2DPSourceAPTXHD() {
+ String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceAPTXHD();
+ }
+
+ /**
+ * Check whether Split A2DP Source APTX ADAPTIVE enabled.
+ *
+ * @return true if Split A2DP Source APTX ADAPTIVE is enabled
+ */
+ public boolean isSplitA2DPSourceAPTXADAPTIVE() {
+ String BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceAPTXADAPTIVE();
+ }
+
+ /**
+ * Check whether Split A2DP Source APTX TWS+ enabled.
+ *
+ * @return true if Split A2DP Source APTX TWS+ is enabled
+ */
+ public boolean isSplitA2DPSourceAPTXTWSPLUS() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSourceAPTXTWSPLUS();
+ }
+
+ /**
+ * Check whether Split A2DP Sink SBC enabled.
+ *
+ * @return true if Split A2DP Sink SBC is enabled
+ */
+ public boolean isSplitA2DPSinkSBC() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkSBC();
+ }
+
+ /**
+ * Check whether Split A2DP Sink MP3 enabled.
+ *
+ * @return true if Split A2DP Sink MP3 is enabled
+ */
+ public boolean isSplitA2DPSinkMP3() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkMP3();
+ }
+
+ /**
+ * Check whether Split A2DP Sink AAC enabled.
+ *
+ * @return true if Split A2DP Sink AAC is enabled
+ */
+ public boolean isSplitA2DPSinkAAC() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkAAC();
+ }
+
+ /**
+ * Check whether Split A2DP Sink LDAC enabled.
+ *
+ * @return true if Split A2DP Sink LDAC is enabled
+ */
+ public boolean isSplitA2DPSinkLDAC() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkLDAC();
+ }
+
+ /**
+ * Check whether Split A2DP Sink APTX enabled.
+ *
+ * @return true if Split A2DP Sink APTX is enabled
+ */
+ public boolean isSplitA2DPSinkAPTX() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkAPTX();
+ }
+
+ /**
+ * Check whether Split A2DP Sink APTX HD enabled.
+ *
+ * @return true if Split A2DP Sink APTX HD is enabled
+ */
+ public boolean isSplitA2DPSinkAPTXHD() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkAPTXHD();
+ }
+
+ /**
+ * Check whether Split A2DP Sink APTX ADAPTIVE enabled.
+ *
+ * @return true if Split A2DP Sink APTX ADAPTIVE is enabled
+ */
+ public boolean isSplitA2DPSinkAPTXADAPTIVE() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkAPTXADAPTIVE();
+ }
+
+ /**
+ * Check whether Split A2DP Sink APTX TWS+ enabled.
+ *
+ * @return true if Split A2DP Sink APTX TWS+ is enabled
+ */
+ public boolean isSplitA2DPSinkAPTXTWSPLUS() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSplitA2DPSinkAPTXTWSPLUS();
+ }
+
+ /**
+ * Check whether Voice Dual SCO enabled.
+ *
+ * @return true if Voice Dual SCO is enabled
+ */
+ public boolean isVoiceDualSCO() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isVoiceDualSCO();
+ }
+
+ /**
+ * Check whether Voice TWS+ eSCO AG enabled.
+ *
+ * @return true if Voice TWS+ eSCO AG is enabled
+ */
+ public boolean isVoiceTWSPLUSeSCOAG() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isVoiceTWSPLUSeSCOAG();
+ }
+
+ /**
+ * Check whether SWB Voice with Aptx Adaptive AG enabled.
+ *
+ * @return true if SWB Voice with Aptx Adaptive AG is enabled
+ */
+ public boolean isSWBVoicewithAptxAdaptiveAG() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isSWBVoicewithAptxAdaptiveAG();
+ }
+
+ /**
+ * Check whether Broadcast Audio Tx with EC-2:5 enabled.
+ *
+ * @return true if Broadcast Audio Tx with EC-2:5 is enabled
+ */
+ public boolean isBroadcastAudioTxwithEC_2_5() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isBroadcastAudioTxwithEC_2_5();
+ }
+
+ /**
+ * Check whether Broadcast Audio Tx with EC_3:9 enabled.
+ *
+ * @return true if Broadcast Audio Tx with EC_3:9 is enabled
+ */
+ public boolean isBroadcastAudioTxwithEC_3_9() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isBroadcastAudioTxwithEC_3_9();
+ }
+
+ /**
+ * Check whether Broadcast Audio Rx with EC_2:5 enabled.
+ *
+ * @return true if Broadcast Audio Rx with EC_2:5 is enabled
+ */
+ public boolean isBroadcastAudioRxwithEC_2_5() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isBroadcastAudioRxwithEC_2_5();
+ }
+
+ /**
+ * Check whether Broadcast Audio Rx with EC_3:9 enabled.
+ *
+ * @return true if Broadcast Audio Rx with EC_3:9 is enabled
+ */
+ public boolean isBroadcastAudioRxwithEC_3_9() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isBroadcastAudioRxwithEC_3_9();
+ }
+
+ /**
+ * Check AddonFeatures Cmd Support.
+ *
+ * @return true if AddonFeatures Cmd is Supported
+ */
+ public boolean isAddonFeaturesCmdSupported() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isAddonFeaturesCmdSupported();
+ }
+
private BluetoothActivityEnergyInfo reportActivityInfo() {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON
@@ -2250,6 +2885,12 @@
return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
}
+ void updateQuietModeStatus(boolean quietMode) {
+ debugLog("updateQuietModeStatus()-updateQuietModeStatus called with quiet mode status:"
+ + quietMode);
+ mQuietmode = quietMode;
+ }
+
void onLeServiceUp() {
mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
}
@@ -2258,6 +2899,19 @@
mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
}
+ int setSocketOpt(int type, int channel, int optionName, byte [] optionVal,
+ int optionLen) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ return mVendorSocket.setSocketOpt(type, channel, optionName, optionVal, optionLen);
+ }
+
+ int getSocketOpt(int type, int channel, int optionName, byte [] optionVal) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ return mVendorSocket.getSocketOpt(type, channel, optionName, optionVal);
+ }
+
private static int convertScanModeToHal(int mode) {
switch (mode) {
case BluetoothAdapter.SCAN_MODE_NONE:
@@ -2508,6 +3162,24 @@
}
};
+ private final BroadcastReceiver mWifiStateBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) && isEnabled()) {
+ NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ NetworkInfo.DetailedState ds = networkInfo.getDetailedState();
+ if (ds == NetworkInfo.DetailedState.CONNECTED) {
+ if(isVendorIntfEnabled())
+ mVendor.setWifiState(true);
+ }
+ else if (ds == NetworkInfo.DetailedState.DISCONNECTED) {
+ if(isVendorIntfEnabled())
+ mVendor.setWifiState(false);
+ }
+ }
+ }
+ };
+
private void enableNativeWithGuestFlag() {
boolean isGuest = UserManager.get(this).isGuestUser();
if (!enableNative(isGuest)) {
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 94feef2..46d8297 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -69,11 +69,21 @@
static final int BREDR_STOP_TIMEOUT = 10;
static final int BLE_STOP_TIMEOUT = 11;
static final int BLE_START_TIMEOUT = 12;
+ static final int BEGIN_BREDR_STOP = 13;
+ static final int STACK_DISABLED = 14;
+ static final int STACK_DISABLE_TIMEOUT = 15;
+ static final int BREDR_CLEANUP_TIMEOUT = 16;
+ static final int BT_FORCEKILL_TIMEOUT = 17;
- static final int BLE_START_TIMEOUT_DELAY = 4000;
+ // TODO: To be optimized : Increased BLE_START_TIMEOUT_DELAY to 6 sec
+ // as OMR1 Total timeout value was 14 seconds
+ static final int BLE_START_TIMEOUT_DELAY = 6000;
static final int BLE_STOP_TIMEOUT_DELAY = 1000;
static final int BREDR_START_TIMEOUT_DELAY = 4000;
static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
+ static final int BREDR_CLEANUP_TIMEOUT_DELAY = 2000;
+ static final int STACK_DISABLE_TIMEOUT_DELAY = 8000;
+ static final int BT_FORCEKILL_TIMEOUT_DELAY = 100;
private AdapterService mAdapterService;
private TurningOnState mTurningOnState = new TurningOnState();
@@ -181,6 +191,11 @@
transitionTo(mTurningBleOnState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
@@ -207,6 +222,12 @@
transitionTo(mTurningBleOffState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
@@ -229,6 +250,12 @@
transitionTo(mTurningOffState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
@@ -266,7 +293,16 @@
case BLE_START_TIMEOUT:
errorLog(messageString(msg.what));
- transitionTo(mTurningBleOffState);
+ mAdapterService.disableProfileServices(true);
+ mAdapterService.StartHCIClose();
+ errorLog("BLE_START_TIMEOUT is going to kill the process as part of cleanup");
+ sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+ break;
+
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
break;
default:
@@ -306,7 +342,16 @@
case BREDR_START_TIMEOUT:
errorLog(messageString(msg.what));
- transitionTo(mTurningOffState);
+ mAdapterService.disableProfileServices(false);
+ mAdapterService.StartHCIClose();
+ errorLog("BREDR_START_TIMEOUT is going to kill the process as part of cleanup");
+ sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+ break;
+
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
break;
default:
@@ -327,8 +372,9 @@
@Override
public void enter() {
super.enter();
- sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
- mAdapterService.stopProfileServices();
+ Log.w(TAG,"Calling startBrEdrCleanup");
+ sendMessageDelayed(BREDR_CLEANUP_TIMEOUT, BREDR_CLEANUP_TIMEOUT_DELAY);
+ mAdapterService.startBrEdrCleanup();
}
@Override
@@ -346,7 +392,30 @@
case BREDR_STOP_TIMEOUT:
errorLog(messageString(msg.what));
- transitionTo(mTurningBleOffState);
+ mAdapterService.disableProfileServices(false);
+ mAdapterService.StartHCIClose();
+ errorLog("BREDR_STOP_TIMEOUT is going to kill the process as part of cleanup");
+ sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+ break;
+
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
+ case BREDR_CLEANUP_TIMEOUT:
+ errorLog("Error cleaningup Bluetooth profiles (cleanup timeout)");
+ mAdapterService.disableProfileServices(false);
+ mAdapterService.StartHCIClose();
+ errorLog("BREDR_CLEANUP_TIMEOUT going to kill the process as part of cleanup");
+ sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+ break;
+
+ case BEGIN_BREDR_STOP:
+ removeMessages(BREDR_CLEANUP_TIMEOUT);
+ sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
+ mAdapterService.stopProfileServices();
break;
default:
@@ -367,8 +436,13 @@
@Override
public void enter() {
super.enter();
- sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
- mAdapterService.bringDownBle();
+ sendMessageDelayed(STACK_DISABLE_TIMEOUT, STACK_DISABLE_TIMEOUT_DELAY);
+ boolean ret = mAdapterService.disableNative();
+ if (!ret) {
+ removeMessages(STACK_DISABLE_TIMEOUT);
+ errorLog("Error while calling disableNative");
+ transitionTo(mBleOnState);
+ }
}
@Override
@@ -385,10 +459,30 @@
break;
case BLE_STOP_TIMEOUT:
+ errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
errorLog(messageString(msg.what));
transitionTo(mOffState);
break;
+ case STACK_DISABLED:
+ removeMessages(STACK_DISABLE_TIMEOUT);
+ sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
+ mAdapterService.bringDownBle();
+ break;
+
+ case STACK_DISABLE_TIMEOUT:
+ mAdapterService.disableProfileServices(true);
+ mAdapterService.StartHCIClose();
+ errorLog("STACK_DISABLE_TIMEOUT going to kill the process as part of cleanup");
+ sendMessageDelayed(BT_FORCEKILL_TIMEOUT, BT_FORCEKILL_TIMEOUT_DELAY);
+ break;
+
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 13ef2ad..6aa0c55 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -1,4 +1,6 @@
/*
+ * Copyright (C) 2018 The Linux Foundation. All rights reserved.
+ * Not a Contribution
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +27,7 @@
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
+import android.os.PowerManager;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
@@ -66,13 +69,20 @@
private RemoteDevices mRemoteDevices;
private BluetoothAdapter mAdapter;
+ /* The WakeLock is used for bringing up the LCD during a pairing request
+ * from remote device when Android is in Suspend state.*/
+ private PowerManager.WakeLock mWakeLock;
+
private PendingCommandState mPendingCommandState = new PendingCommandState();
private StableState mStableState = new StableState();
public static final String OOBDATA = "oobdata";
- private BondStateMachine(AdapterService service, AdapterProperties prop,
- RemoteDevices remoteDevices) {
+ private final ArrayList<BluetoothDevice> mDevices =
+ new ArrayList<BluetoothDevice>();
+
+ private BondStateMachine(PowerManager pm, AdapterService service,
+ AdapterProperties prop, RemoteDevices remoteDevices) {
super("BondStateMachine:");
addState(mStableState);
addState(mPendingCommandState);
@@ -81,17 +91,22 @@
mAdapterProperties = prop;
mAdapter = BluetoothAdapter.getDefaultAdapter();
setInitialState(mStableState);
+
+ //WakeLock instantiation in RemoteDevices class
+ mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
+ | PowerManager.ON_AFTER_RELEASE, TAG);
+ mWakeLock.setReferenceCounted(false);
}
- public static BondStateMachine make(AdapterService service, AdapterProperties prop,
- RemoteDevices remoteDevices) {
+ public static BondStateMachine make(PowerManager pm, AdapterService service,
+ AdapterProperties prop, RemoteDevices remoteDevices) {
Log.d(TAG, "make");
- BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
+ BondStateMachine bsm = new BondStateMachine(pm, service, prop, remoteDevices);
bsm.start();
return bsm;
}
- public void doQuit() {
+ public synchronized void doQuit() {
quitNow();
}
@@ -113,7 +128,7 @@
}
@Override
- public boolean processMessage(Message msg) {
+ public synchronized boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice) msg.obj;
@@ -132,12 +147,15 @@
break;
case BONDING_STATE_CHANGE:
int newState = msg.arg1;
- /* if incoming pairing, transition to pending state */
+ /* if incoming pairing, transition to pending state */
if (newState == BluetoothDevice.BOND_BONDING) {
+ if (!mDevices.contains(dev)) {
+ mDevices.add(dev);
+ }
sendIntent(dev, newState, 0);
transitionTo(mPendingCommandState);
} else if (newState == BluetoothDevice.BOND_NONE) {
- /* if the link key was deleted by the stack */
+ /* if the link key was deleted by the stack */
sendIntent(dev, newState, 0);
} else {
Log.e(TAG, "In stable state, received invalid newState: "
@@ -156,7 +174,6 @@
private class PendingCommandState extends State {
- private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();
@Override
public void enter() {
@@ -165,7 +182,7 @@
}
@Override
- public boolean processMessage(Message msg) {
+ public synchronized boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice) msg.obj;
DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
boolean result = false;
@@ -196,6 +213,14 @@
int reason = getUnbondReasonFromHALCode(msg.arg2);
sendIntent(dev, newState, reason);
if (newState != BluetoothDevice.BOND_BONDING) {
+ // check if bond none is received from device which
+ // was in pairing state otherwise don't transition to
+ // stable state.
+ if (newState == BluetoothDevice.BOND_NONE &&
+ !mDevices.contains(dev) && mDevices.size() != 0) {
+ infoLog("not transitioning to stable state");
+ break;
+ }
/* this is either none/bonded, remove and transition */
result = !mDevices.remove(dev);
if (mDevices.isEmpty()) {
@@ -223,11 +248,21 @@
case SSP_REQUEST:
int passkey = msg.arg1;
int variant = msg.arg2;
+ if (devProp == null)
+ {
+ Log.e(TAG,"Received msg from an unknown device");
+ return false;
+ }
sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
break;
case PIN_REQUEST:
BluetoothClass btClass = dev.getBluetoothClass();
int btDeviceClass = btClass.getDeviceClass();
+ if (devProp == null)
+ {
+ Log.e(TAG,"Received msg from an unknown device");
+ return false;
+ }
if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass
== BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
// Its a keyboard. Follow the HID spec recommendation of creating the
@@ -267,6 +302,7 @@
}
private boolean cancelBond(BluetoothDevice dev) {
+ if (mAdapterService == null) return false;
if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
if (!mAdapterService.cancelBondNative(addr)) {
@@ -279,6 +315,7 @@
}
private boolean removeBond(BluetoothDevice dev, boolean transition) {
+ if (mAdapterService == null) return false;
if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
if (!mAdapterService.removeBondNative(addr)) {
@@ -286,6 +323,7 @@
} else {
if (transition) {
transitionTo(mPendingCommandState);
+ dev.setAlias(null);
}
return true;
}
@@ -296,6 +334,7 @@
private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
boolean transition) {
+ if (mAdapterService == null) return false;
if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
infoLog("Bond address is:" + dev);
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
@@ -318,6 +357,9 @@
}
private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
+
+ // Acquire wakelock during PIN code request to bring up LCD display
+ mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
if (pin != 0) {
@@ -328,6 +370,8 @@
// Workaround for Android Auto until pre-accepting pairing requests is added.
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
+ // Release wakelock to allow the LCD to go off after the PIN popup notification.
+ mWakeLock.release();
}
private void sendIntent(BluetoothDevice device, int newState, int reason) {
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 8a9c0a1..7b6f37b 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -23,6 +23,7 @@
import android.provider.Settings;
import android.util.FeatureFlagUtils;
import android.util.Log;
+import android.os.SystemProperties;
import com.android.bluetooth.R;
import com.android.bluetooth.a2dp.A2dpService;
@@ -43,6 +44,7 @@
import com.android.bluetooth.pbap.BluetoothPbapService;
import com.android.bluetooth.pbapclient.PbapClientService;
import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.ba.BATService;
import java.util.ArrayList;
@@ -101,7 +103,9 @@
new ProfileConfig(BluetoothPbapService.class, R.bool.profile_supported_pbap,
(1 << BluetoothProfile.PBAP)),
new ProfileConfig(HearingAidService.class, R.bool.profile_supported_hearing_aid,
- (1 << BluetoothProfile.HEARING_AID))
+ (1 << BluetoothProfile.HEARING_AID)),
+ new ProfileConfig(BATService.class, R.bool.profile_supported_ba,
+ (1 << BluetoothProfile.BA_TRANSMITTER))
};
private static Class[] sSupportedProfiles = new Class[0];
@@ -126,6 +130,10 @@
}
if (supported && !isProfileDisabled(ctx, config.mMask)) {
+ if (!addAudioProfiles(config.mClass.getSimpleName())) {
+ Log.i(TAG, " Profile " + config.mClass.getSimpleName() + " Not added ");
+ continue;
+ }
Log.v(TAG, "Adding " + config.mClass.getSimpleName());
profiles.add(config.mClass);
}
@@ -162,4 +170,38 @@
return (disabledProfilesBitMask & profileMask) != 0;
}
+
+ private static synchronized boolean addAudioProfiles(String serviceName) {
+ Log.d(TAG," addAudioProfiles profile" + serviceName);
+ boolean isA2dpConcurrency= SystemProperties.getBoolean(
+ "persist.vendor.service.bt.a2dp_concurrency", false);
+ Log.i(TAG, "addAudioProfiles isA2dpConcurrency:" + isA2dpConcurrency);
+
+ if(isA2dpConcurrency) {
+ if ((serviceName.equals("A2dpSinkService")) || (serviceName.equals("A2dpService"))) {
+ return true;
+ }
+ } else {
+ boolean isA2dpSink = SystemProperties.getBoolean(
+ "persist.vendor.service.bt.a2dp.sink", false);
+ Log.i(TAG, "addAudioProfiles isA2dpSink :" + isA2dpSink);
+ /* If property not enabled and request is for A2DPSinkService, don't add */
+ if ((serviceName.equals("A2dpSinkService")) && (!isA2dpSink))
+ return false;
+ if ((serviceName.equals("A2dpService")) && (isA2dpSink))
+ return false;
+ }
+
+ boolean isBAEnabled = SystemProperties.getBoolean("persist.vendor.service.bt.bca", false);
+ boolean isSplitA2dpSupported = SystemProperties.
+ getBoolean("persist.vendor.btstack.enable.splita2dp", true);
+
+ if(serviceName.equals("BATService")) {
+ Log.d(TAG," isBAEnabled = " + isBAEnabled
+ + " isSplitEnabled " + isSplitA2dpSupported);
+ return isBAEnabled && isSplitA2dpSupported;
+ }
+ // always return true for other profiles
+ return true;
+ }
}
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index a0ad490..7cd45f9 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2017 The Android Open Source Project
*
@@ -17,6 +18,7 @@
package com.android.bluetooth.btservice;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -35,10 +37,12 @@
import android.util.Log;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidHostService;
import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.ba.BATService;
import com.android.internal.R;
import java.util.HashSet;
@@ -78,10 +82,17 @@
private static final int MESSAGE_PROFILE_INIT_PRIORITIES = 2;
private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
- private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+ private static final int MESSAGE_AUTO_CONNECT_PROFILES = 50;
// Timeouts
+ private static final int AUTO_CONNECT_PROFILES_TIMEOUT= 500;
@VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
+ private static final int CONNECT_OTHER_PROFILES_TIMEOUT_DELAYED = 10000; //10s
+ private static final int CONNECT_OTHER_PROFILES_REDUCED_TIMEOUT_DELAYED = 2000; //2s
+
+ private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+ private static final String delayConnectTimeoutDevice[] = {"00:23:3D"}; // volkswagen carkit
+ private static final String delayReducedConnectTimeoutDevice[] = {"10:4F:A8"}; //h.ear (MDR-EX750BT)
private final AdapterService mAdapterService;
private final ServiceFactory mFactory;
@@ -110,11 +121,21 @@
BluetoothProfile.A2DP, -1, // No-op argument
intent).sendToTarget();
break;
+ case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
+ BluetoothProfile.A2DP_SINK,-1, // No-op argument
+ intent).sendToTarget();
+ break;
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
BluetoothProfile.A2DP, -1, // No-op argument
intent).sendToTarget();
break;
+ case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.HEADSET, -1, // No-op argument
+ intent).sendToTarget();
+ break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
// Only pass the message on if the adapter has actually changed state from
// non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
@@ -168,6 +189,10 @@
Intent intent = (Intent) msg.obj;
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device.getAddress().equals(BATService.mBAAddress)) {
+ Log.d(TAG," Update from BA, bail out");
+ break;
+ }
int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
processProfileStateChanged(device, msg.arg1, nextState, prevState);
@@ -195,6 +220,11 @@
resetStates();
autoConnect();
break;
+ case MESSAGE_AUTO_CONNECT_PROFILES: {
+ if (DBG) debugLog( "MESSAGE_AUTO_CONNECT_PROFILES");
+ autoConnectProfilesDelayed();
+ break;
+ }
}
}
}
@@ -206,9 +236,11 @@
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_UUID);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
mAdapterService.registerReceiver(mReceiver, filter);
}
@@ -228,9 +260,14 @@
debugLog("processInitProfilePriorities() - device " + device);
HidHostService hidService = mFactory.getHidHostService();
A2dpService a2dpService = mFactory.getA2dpService();
+ A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
HeadsetService headsetService = mFactory.getHeadsetService();
PanService panService = mFactory.getPanService();
HearingAidService hearingAidService = mFactory.getHearingAidService();
+ BluetoothDevice peerTwsDevice = null;
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+ }
// Set profile priorities only for the profiles discovered on the remote device.
// This avoids needless auto-connect attempts to profiles non-existent on the remote device
@@ -245,12 +282,27 @@
|| BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) && (
headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (peerTwsDevice != null) {
+ debugLog("setting peer earbud to priority on for hfp" + peerTwsDevice);
+ headsetService.setPriority(peerTwsDevice, BluetoothProfile.PRIORITY_ON);
+ }
}
if ((a2dpService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)
|| BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (peerTwsDevice != null) {
+ debugLog("setting peer earbud to priority on for a2dp" + peerTwsDevice);
+ a2dpService.setPriority(peerTwsDevice, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+
+ if ((a2dpSinkService != null)
+ && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)
+ || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
+ a2dpSinkService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
+ a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
@@ -272,14 +324,25 @@
int prevState) {
debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
+ prevState + " -> " + nextState);
- if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) {
+ if ((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)
+ || profileId == BluetoothProfile.A2DP_SINK) {
if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ debugLog("processProfileStateChanged: isTwsDevice: " + mAdapterService.isTwsPlusDevice(device));
switch (profileId) {
case BluetoothProfile.A2DP:
mA2dpRetrySet.remove(device);
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ setAutoConnectForA2dpSink(device);
+ }
break;
case BluetoothProfile.HEADSET:
mHeadsetRetrySet.remove(device);
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ setAutoConnectForHeadset(device);
+ }
+ break;
+ case BluetoothProfile.A2DP_SINK:
+ setAutoConnectForA2dpSource(device);
break;
}
connectOtherProfile(device);
@@ -296,6 +359,9 @@
debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
+ a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
if (hsDisconnected && a2dpDisconnected) {
+ //remove a2dp and headset retry set.
+ mA2dpRetrySet.remove(device);
+ mHeadsetRetrySet.remove(device);
removeAutoConnectFromA2dpSink(device);
removeAutoConnectFromHeadset(device);
}
@@ -304,6 +370,8 @@
}
private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
+ HeadsetService hsService = mFactory.getHeadsetService();
+ A2dpService a2dpService = mFactory.getA2dpService();
debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
+ profileId);
switch (profileId) {
@@ -322,6 +390,43 @@
}
setAutoConnectForA2dpSink(activeDevice);
setAutoConnectForHeadset(activeDevice);
+ if ((mAdapterService != null) &&
+ (mAdapterService.isTwsPlusDevice(activeDevice))) {
+ BluetoothDevice peerTwsDevice =
+ mAdapterService.getTwsPlusPeerDevice(activeDevice);
+ if (peerTwsDevice != null) {
+ if (a2dpService != null &&
+ a2dpService.getConnectionState(peerTwsDevice) !=
+ BluetoothProfile.STATE_DISCONNECTED) {
+ debugLog("A2DP: Set Autoconnect for Peer TWS+ as well");
+ setAutoConnectForA2dpSink(peerTwsDevice);
+ }
+ if (hsService != null &&
+ hsService.getConnectionState(peerTwsDevice) !=
+ BluetoothProfile.STATE_DISCONNECTED) {
+ debugLog("HFP: Set Autoconnect for Peer TWS+ as well");
+ setAutoConnectForHeadset(peerTwsDevice);
+ }
+ }
+ }
+ break;
+ case BluetoothProfile.HEADSET:
+ // Ignore null active device since we don't know if the change is triggered by
+ // normal device disconnection during Bluetooth shutdown or user action
+ if (activeDevice == null) {
+ warnLog("processProfileActiveDeviceChanged: ignore null HFP active device");
+ return;
+ }
+ // If a device with only HFP profile is connected then set autoconnection for
+ // that device.
+ if (a2dpService != null) {
+ if (a2dpService.getConnectedDevices().size() == 0) {
+ warnLog("processProfileActiveDeviceChanged: HFP active device changed and"+
+ " no A2DP device connected, so setting priority to auto connect for HFP"+
+ " device: " + activeDevice);
+ setAutoConnectForHeadset(activeDevice);
+ }
+ }
break;
}
}
@@ -331,7 +436,18 @@
mA2dpRetrySet.clear();
}
- private void autoConnect() {
+ // Delaying Auto Connect to make sure that all clients
+ // are up and running, specially BluetoothHeadset.
+ public void autoConnect() {
+ debugLog( "delay auto connect by 500 ms");
+ if ((mHandler.hasMessages(MESSAGE_AUTO_CONNECT_PROFILES) == false) &&
+ (mAdapterService.isQuietModeEnabled()== false)) {
+ Message m = mHandler.obtainMessage(MESSAGE_AUTO_CONNECT_PROFILES);
+ mHandler.sendMessageDelayed(m,AUTO_CONNECT_PROFILES_TIMEOUT);
+ }
+ }
+
+ private void autoConnectProfilesDelayed() {
if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
errorLog("autoConnect: BT is not ON. Exiting autoConnect");
return;
@@ -339,6 +455,8 @@
if (!mAdapterService.isQuietModeEnabled()) {
debugLog("autoConnect: Initiate auto connection on BT on...");
+ //Remote Device Profiles
+ autoConnectA2dpSink();
final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
if (bondedDevices == null) {
errorLog("autoConnect: bondedDevices are null");
@@ -385,6 +503,51 @@
}
}
+ private void autoConnectA2dpSink() {
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if (a2dpSinkService == null) {
+ errorLog("autoConnectA2dpSink, service is null");
+ return;
+ }
+ BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices();
+ if (bondedDevices == null) {
+ errorLog("autoConnectA2dpSink, bondedDevices are null");
+ return;
+ }
+
+ for (BluetoothDevice device : bondedDevices) {
+ int priority = a2dpSinkService.getPriority(device);
+ debugLog("autoConnectA2dpSink, attempt auto-connect with device " + device
+ + " priority " + priority);
+ if (priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ debugLog("autoConnectA2dpSink() - Connecting A2DP Sink with " + device.toString());
+ a2dpSinkService.connect(device);
+ }
+ }
+ }
+
+ private boolean isConnectTimeoutDelayApplicable(BluetoothDevice device){
+ boolean isConnectionTimeoutDelayed = false;
+ String deviceAddress = device.getAddress();
+ for (int i = 0; i < delayConnectTimeoutDevice.length;i++) {
+ if (deviceAddress.indexOf(delayConnectTimeoutDevice[i]) == 0) {
+ isConnectionTimeoutDelayed = true;
+ }
+ }
+ return isConnectionTimeoutDelayed;
+ }
+
+ private boolean isConnectReducedTimeoutDelayApplicable(BluetoothDevice device){
+ boolean isConnectionReducedTimeoutDelayed = false;
+ String deviceAddress = device.getAddress();
+ for (int i = 0; i < delayReducedConnectTimeoutDevice.length;i++) {
+ if (deviceAddress.indexOf(delayReducedConnectTimeoutDevice[i]) == 0) {
+ isConnectionReducedTimeoutDelayed = true;
+ }
+ }
+ return isConnectionReducedTimeoutDelayed;
+ }
+
private void connectOtherProfile(BluetoothDevice device) {
if (mAdapterService.isQuietModeEnabled()) {
debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
@@ -397,7 +560,12 @@
mConnectOtherProfilesDeviceSet.add(device);
Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
m.obj = device;
- mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
+ if (isConnectTimeoutDelayApplicable(device))
+ mHandler.sendMessageDelayed(m,CONNECT_OTHER_PROFILES_TIMEOUT_DELAYED);
+ else if (isConnectReducedTimeoutDelayApplicable(device))
+ mHandler.sendMessageDelayed(m,CONNECT_OTHER_PROFILES_REDUCED_TIMEOUT_DELAYED);
+ else
+ mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
}
// This function is called whenever a profile is connected. This allows any other bluetooth
@@ -413,10 +581,15 @@
HeadsetService hsService = mFactory.getHeadsetService();
A2dpService a2dpService = mFactory.getA2dpService();
PanService panService = mFactory.getPanService();
+ A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+
+ boolean a2dpConnected = false;
+ boolean hsConnected = false;
boolean atLeastOneProfileConnectedForDevice = false;
boolean allProfilesEmpty = true;
List<BluetoothDevice> a2dpConnDevList = null;
+ List<BluetoothDevice> a2dpSinkConnDevList = null;
List<BluetoothDevice> hsConnDevList = null;
List<BluetoothDevice> panConnDevList = null;
@@ -430,6 +603,11 @@
allProfilesEmpty &= a2dpConnDevList.isEmpty();
atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
}
+ if (a2dpSinkService != null) {
+ a2dpSinkConnDevList = a2dpSinkService.getConnectedDevices();
+ allProfilesEmpty &= a2dpSinkConnDevList.isEmpty();
+ atLeastOneProfileConnectedForDevice |= a2dpSinkConnDevList.contains(device);
+ }
if (panService != null) {
panConnDevList = panService.getConnectedDevices();
allProfilesEmpty &= panConnDevList.isEmpty();
@@ -449,22 +627,93 @@
return;
}
- if (hsService != null) {
- if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
- >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
- == BluetoothProfile.STATE_DISCONNECTED)) {
- debugLog("Retrying connection to Headset with device " + device);
- mHeadsetRetrySet.add(device);
- hsService.connect(device);
+ if(a2dpConnDevList != null && !a2dpConnDevList.isEmpty()) {
+ for (BluetoothDevice a2dpDevice : a2dpConnDevList)
+ {
+ if(a2dpDevice.equals(device))
+ {
+ a2dpConnected = true;
+ }
}
}
+
+ if(hsConnDevList != null && !hsConnDevList.isEmpty()) {
+ for (BluetoothDevice hsDevice : hsConnDevList)
+ {
+ if(hsDevice.equals(device))
+ {
+ hsConnected = true;
+ }
+ }
+ }
+
+ // This change makes sure that we try to re-connect
+ // the profile if its connection failed and priority
+ // for desired profile is ON.
+ debugLog("HF connected for device : " + device + " " +
+ (hsConnDevList == null ? false :hsConnDevList.contains(device)));
+ debugLog("A2DP connected for device : " + device + " " +
+ (a2dpConnDevList == null ? false :a2dpConnDevList.contains(device)));
+ debugLog("A2DPSink connected for device : " + device + " " +
+ (a2dpSinkConnDevList == null ? false :a2dpSinkConnDevList.contains(device)));
+
+ if (hsService != null) {
+ if ((hsConnDevList.isEmpty() || !(hsConnDevList.contains(device)))
+ && (!mHeadsetRetrySet.contains(device))
+ && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)
+ && (hsService.getConnectionState(device)
+ == BluetoothProfile.STATE_DISCONNECTED)
+ && (a2dpConnected || (a2dpService != null &&
+ a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
+
+ debugLog("Retrying connection to HS with device " + device);
+ int maxConnections = mAdapterService.getMaxConnectedAudioDevices();
+
+ if (!hsConnDevList.isEmpty() && maxConnections == 1) {
+ Log.v(TAG,"HFP is already connected, ignore");
+ return;
+ }
+
+ // proceed connection only if a2dp is connected to this device
+ // add here as if is already overloaded
+ if (a2dpConnDevList.contains(device) ||
+ (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)) {
+ debugLog("Retrying connection to HS with device " + device);
+ mHeadsetRetrySet.add(device);
+ hsService.connect(device);
+ } else {
+ debugLog("do not initiate connect as A2dp is not connected");
+ }
+ }
+ }
+
if (a2dpService != null) {
- if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
- >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
- == BluetoothProfile.STATE_DISCONNECTED)) {
+ if ((a2dpConnDevList.isEmpty() || !(a2dpConnDevList.contains(device)))
+ && (!mA2dpRetrySet.contains(device))
+ && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON ||
+ mAdapterService.isTwsPlusDevice(device))
+ && (a2dpService.getConnectionState(device)
+ == BluetoothProfile.STATE_DISCONNECTED)
+ && (hsConnected || (hsService != null &&
+ hsService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
debugLog("Retrying connection to A2DP with device " + device);
- mA2dpRetrySet.add(device);
- a2dpService.connect(device);
+ int maxConnections = mAdapterService.getMaxConnectedAudioDevices();
+
+ if (!a2dpConnDevList.isEmpty() && maxConnections == 1) {
+ Log.v(TAG,"a2dp is already connected, ignore");
+ return;
+ }
+
+ // proceed connection only if HFP is connected to this device
+ // add here as if is already overloaded
+ if (hsConnDevList.contains(device) ||
+ (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)) {
+ debugLog("Retrying connection to A2DP with device " + device);
+ mA2dpRetrySet.add(device);
+ a2dpService.connect(device);
+ } else {
+ debugLog("do not initiate connect as HFP is not connected");
+ }
}
}
if (panService != null) {
@@ -477,8 +726,67 @@
panService.connect(device);
}
}
+ // Connect A2DP Sink Service if HS is connected
+ if (a2dpSinkService != null) {
+ List<BluetoothDevice> sinkConnDevList = a2dpSinkService.getConnectedDevices();
+ if (sinkConnDevList.isEmpty() &&
+ (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) &&
+ (a2dpSinkService.getConnectionState(device) ==
+ BluetoothProfile.STATE_DISCONNECTED) &&
+ (hsConnected || (hsService != null &&
+ hsService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) {
+ debugLog("Retrying connection for A2dpSink with device " + device);
+ a2dpSinkService.connect(device);
+ }
+ }
+
}
+ private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId,
+ boolean autoConnect) {
+ debugLog("setProfileAutoConnectionPriority: device=" + device + ", profile=" + profileId
+ + ", autoConnect=" + autoConnect);
+ switch (profileId) {
+ case BluetoothProfile.HEADSET: {
+ HeadsetService hsService = mFactory.getHeadsetService();
+ if (hsService == null) {
+ warnLog("setProfileAutoConnectionPriority: HEADSET service is null");
+ break;
+ }
+ removeAutoConnectFromDisconnectedHeadsets(hsService);
+ if (autoConnect) {
+ hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+ }
+ case BluetoothProfile.A2DP: {
+ A2dpService a2dpService = mFactory.getA2dpService();
+ if (a2dpService == null) {
+ warnLog("setProfileAutoConnectionPriority: A2DP service is null");
+ break;
+ }
+ removeAutoConnectFromDisconnectedA2dpSinks(a2dpService);
+ if (autoConnect) {
+ a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+ }
+ case BluetoothProfile.A2DP_SINK: {
+ A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+ if (a2dpSinkService != null) {
+ if (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpSinkService.getPriority(
+ device)) {
+ adjustOtherSourcePriorities(a2dpSinkService, a2dpSinkService.getConnectedDevices());
+ a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ }
+ break;
+ }
+ default:
+ Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId);
+ break;
+ }
+ }
/**
* Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
* profile
@@ -515,6 +823,21 @@
}
/**
+ * Set device A2DP SINK priority to PRIORITY_AUTO_CONNECT if role is A2DP Sink
+ */
+ private void setAutoConnectForA2dpSource(BluetoothDevice device) {
+ A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+ if (a2dpSinkService == null) {
+ warnLog("setAutoConnectForA2dpSink: A2DP service is null");
+ return;
+ }
+ if (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
+ debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
+ a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ }
+
+ /**
* Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
* PRIORITY_AUTO_CONNECT to PRIORITY_ON
*
@@ -550,6 +873,40 @@
}
}
+ private void adjustOtherSourcePriorities(
+ A2dpSinkService a2dpSinkService, List<BluetoothDevice> connectedDeviceList) {
+ for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+ if (a2dpSinkService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+ && !connectedDeviceList.contains(device)) {
+ a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
+ private void removeAutoConnectFromDisconnectedHeadsets(HeadsetService hsService) {
+ List<BluetoothDevice> connectedDeviceList = hsService.getConnectedDevices();
+ for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+ if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+ && !connectedDeviceList.contains(device)) {
+ debugLog("removeAutoConnectFromDisconnectedHeadsets, device " + device
+ + " PRIORITY_ON");
+ hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
+ private void removeAutoConnectFromDisconnectedA2dpSinks(A2dpService a2dpService) {
+ List<BluetoothDevice> connectedDeviceList = a2dpService.getConnectedDevices();
+ for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+ if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
+ && !connectedDeviceList.contains(device)) {
+ debugLog("removeAutoConnectFromDisconnectedA2dpSinks, device " + device
+ + " PRIORITY_ON");
+ a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
private static void debugLog(String msg) {
if (DBG) {
Log.i(TAG, msg);
diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java
index 9a7186c..92cc261 100644
--- a/src/com/android/bluetooth/btservice/ProfileService.java
+++ b/src/com/android/bluetooth/btservice/ProfileService.java
@@ -54,7 +54,7 @@
//Profile services will not be automatically restarted.
//They must be explicitly restarted by AdapterService
private static final int PROFILE_SERVICE_MODE = Service.START_NOT_STICKY;
- private BluetoothAdapter mAdapter;
+ protected BluetoothAdapter mAdapter;
private IProfileServiceBinder mBinder;
private final String mName;
private AdapterService mAdapterService;
@@ -130,7 +130,7 @@
if (DBG) {
Log.d(mName, "onStartCommand()");
}
-
+ AdapterService adapterService = AdapterService.getAdapterService();
if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)
!= PackageManager.PERMISSION_GRANTED) {
Log.e(mName, "Permission denied!");
@@ -145,10 +145,31 @@
String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);
if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+ int currentState = (adapterService != null) ? adapterService.getState() : -1;
if (state == BluetoothAdapter.STATE_OFF) {
- doStop();
+ if ((currentState == BluetoothAdapter.STATE_TURNING_OFF &&
+ !mName.equals("GattService")) ||
+ (currentState == BluetoothAdapter.STATE_BLE_TURNING_OFF &&
+ mName.equals("GattService")) ) {
+ Log.d(mName, ": Received stop request...Stopping profile...");
+ doStop();
+ } else {
+ Log.e(mName, ":intent received late, not Stopping profile");
+ }
} else if (state == BluetoothAdapter.STATE_ON) {
- doStart();
+ if ((currentState == BluetoothAdapter.STATE_TURNING_ON &&
+ !mName.equals("GattService")) ||
+ (currentState == BluetoothAdapter.STATE_BLE_TURNING_ON &&
+ mName.equals("GattService")) ) {
+
+ Log.d(mName, "Received start request. Starting profile...");
+ doStart();
+ } else {
+ Log.e(mName, ":intent received late, not starting profile");
+ if (adapterService != null) {
+ adapterService.removeProfile(this);
+ }
+ }
}
}
return PROFILE_SERVICE_MODE;
@@ -265,6 +286,7 @@
Log.e(mName, "Error starting profile. start() returned false.");
return;
}
+ Log.d(mName, " profile started successfully");
mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_ON);
}
@@ -273,11 +295,13 @@
Log.w(mName, "doStop() called, but the profile is not running.");
}
mProfileStarted = false;
- if (mAdapterService != null) {
- mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF);
- }
if (!stop()) {
Log.e(mName, "Unable to stop profile");
+ } else {
+ Log.d(mName, " profile stopped successfully");
+ }
+ if (mAdapterService != null) {
+ mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF);
}
if (mAdapterService != null) {
mAdapterService.removeProfile(this);
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 12897fe..356c8b1 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -1,4 +1,39 @@
/*
+ * Copyright (C) 2017, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
* Copyright (C) 2012-2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -107,6 +142,12 @@
}
};
+ public static final String ACTION_TWS_PLUS_DEVICE_PAIR =
+ "android.bluetooth.device.action.TWS_PLUS_DEVICE_PAIR";
+ public static final String EXTRA_TWS_PLUS_DEVICE1 =
+ "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE1";
+ public static final String EXTRA_TWS_PLUS_DEVICE2 =
+ "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE2";
RemoteDevices(AdapterService service, Looper looper) {
sAdapter = BluetoothAdapter.getDefaultAdapter();
sAdapterService = service;
@@ -170,11 +211,14 @@
}
BluetoothDevice getDevice(byte[] address) {
- DeviceProperties prop = mDevices.get(Utils.getAddressStringFromByte(address));
- if (prop == null) {
- return null;
+ DeviceProperties prop;
+ synchronized (mDevices) {
+ prop = mDevices.get(Utils.getAddressStringFromByte(address));
}
- return prop.getDevice();
+ if (prop != null) {
+ return prop.getDevice();
+ }
+ return null;
}
DeviceProperties addDeviceProperties(byte[] address) {
@@ -214,9 +258,15 @@
private BluetoothDevice mDevice;
private boolean mIsBondingInitiatedLocally;
private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ private short mTwsPlusDevType;
+ private byte[] peerEbAddress;
+ private boolean autoConnect;
DeviceProperties() {
mBondState = BluetoothDevice.BOND_NONE;
+ mTwsPlusDevType = AbstractionLayer.TWS_PLUS_DEV_TYPE_NONE;
+ autoConnect = true;
+ peerEbAddress = null;
}
/**
@@ -297,6 +347,8 @@
void setAlias(BluetoothDevice device, String mAlias) {
synchronized (mObject) {
this.mAlias = mAlias;
+ if (mAlias == null)
+ return;
sAdapterService.setDevicePropertyNative(mAddress,
AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes());
Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
@@ -307,6 +359,72 @@
}
/**
+ * @return mTwsPlusDevType
+ */
+ int getTwsPlusDevType() {
+ synchronized (mObject) {
+ return mTwsPlusDevType;
+ }
+ }
+
+ /**
+ * @return peerEbAddress
+ */
+ byte[] getTwsPlusPeerAddress() {
+ synchronized (mObject) {
+ return peerEbAddress;
+ }
+ }
+
+ /**
+ * @param mTwsPlusDevType the mTwsPlusDevType to set
+ */
+ void setTwsPlusDevType(short twsPlusDevType) {
+ synchronized (mObject) {
+ this.mTwsPlusDevType = twsPlusDevType;
+ if(twsPlusDevType == AbstractionLayer.TWS_PLUS_DEV_TYPE_NONE) {
+ this.peerEbAddress = null;
+ }
+ }
+ }
+
+ /**
+ * @param peerEbAddress the peerEbAddress to set
+ */
+ void setTwsPlusPeerEbAddress(BluetoothDevice device, byte[] peerEbAddress) {
+ synchronized (mObject) {
+ Intent intent;
+
+ /* in case of null null bd address reset the address */
+ if (peerEbAddress != null &&
+ Utils.getAddressStringFromByte(peerEbAddress).equals("00:00:00:00:00:00")) {
+ this.peerEbAddress = null;
+ errorLog(" resetting the peerEbAddress to null");
+ } else {
+ this.peerEbAddress = peerEbAddress;
+ if(device != null && peerEbAddress != null) {
+ errorLog(" Peer EB Address is:" +
+ Utils.getAddressStringFromByte(peerEbAddress));
+ intent = new Intent(ACTION_TWS_PLUS_DEVICE_PAIR);
+ intent.putExtra(EXTRA_TWS_PLUS_DEVICE1, mDevice);
+ intent.putExtra(EXTRA_TWS_PLUS_DEVICE2, device);
+ sAdapterService.sendBroadcast(intent,
+ AdapterService.BLUETOOTH_ADMIN_PERM);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param peerEbAddress the peerEbAddress to set
+ */
+ void setTwsPlusAutoConnect(BluetoothDevice device, boolean autoConnect) {
+ synchronized (mObject) {
+ this.autoConnect = autoConnect;
+ debugLog("sendUuidIntent as Auto connect " + autoConnect );
+ }
+ }
+ /*
* @param mBondState the mBondState to set
*/
void setBondState(int mBondState) {
@@ -373,7 +491,9 @@
sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
//Remove the outstanding UUID request
- sSdpTracker.remove(device);
+ if (sSdpTracker.contains(device)) {
+ sSdpTracker.remove(device);
+ }
}
/**
@@ -487,6 +607,11 @@
device = getDeviceProperties(bdDevice);
}
+ if (device == null) {
+ errorLog("device null ");
+ return;
+ }
+
if (types.length <= 0) {
errorLog("No properties to update");
return;
@@ -544,7 +669,9 @@
break;
}
device.mUuids = newUuids;
- if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
+ if ((sAdapterService.getState() == BluetoothAdapter.STATE_ON) &&
+ device.autoConnect ) {
+ debugLog("sendUuidIntent as Auto connect is set ");
sendUuidIntent(bdDevice);
}
break;
@@ -608,13 +735,6 @@
"aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
+ " Connected: " + device);
} else {
- if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
- // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding.
- intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package));
- sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
- }
if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) {
intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
} else if (state == BluetoothAdapter.STATE_BLE_ON
diff --git a/src/com/android/bluetooth/btservice/ServiceFactory.java b/src/com/android/bluetooth/btservice/ServiceFactory.java
index 2bd7ab5..580de3b 100644
--- a/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.btservice;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidDeviceService;
@@ -48,4 +49,8 @@
public HearingAidService getHearingAidService() {
return HearingAidService.getHearingAidService();
}
+
+ public A2dpSinkService getA2dpSinkService() {
+ return A2dpSinkService.getA2dpSinkService();
+ }
}
diff --git a/src/com/android/bluetooth/gatt/AdvertiseHelper.java b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
index 1c1b4dc..a71de5b 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseHelper.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
@@ -39,6 +39,7 @@
private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;
+ private static final int TRANSPORT_DISCOVERY_DATA = 0X26;
public static byte[] advertiseDataToBytes(AdvertiseData data, String name) {
@@ -166,6 +167,13 @@
}
}
+ byte[] transportDiscoveryData = data.getTransportDiscoveryData();
+ if (transportDiscoveryData != null && (transportDiscoveryData.length > 0)) {
+ ret.write(transportDiscoveryData.length + 1);
+ ret.write(TRANSPORT_DISCOVERY_DATA);
+ ret.write(transportDiscoveryData, 0, transportDiscoveryData.length);
+ }
+
return ret.toByteArray();
}
}
diff --git a/src/com/android/bluetooth/gatt/AdvertiseManager.java b/src/com/android/bluetooth/gatt/AdvertiseManager.java
index 85917a4..11a8ba1 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -394,6 +394,29 @@
callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
}
+ void stopAdvertisingSets() {
+ Log.d(TAG, "stopAdvertisingSets()");
+ for (Map.Entry<IBinder, AdvertiserInfo> entry : mAdvertisers.entrySet()) {
+ Integer advertiser_id = entry.getValue().id;
+ IAdvertisingSetCallback callback = entry.getValue().callback;
+ IBinder binder = toBinder(callback);
+ binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
+
+ if (advertiser_id < 0) {
+ Log.i(TAG, "stopAdvertisingSets() - advertiser not finished registration yet");
+ continue;
+ }
+
+ stopAdvertisingSetNative(advertiser_id);
+
+ try {
+ callback.onAdvertisingSetStopped(advertiser_id);
+ } catch (RemoteException e) {
+ Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
+ }
+ }
+ }
+
static {
classInitNative();
}
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index af59262..262e76e 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -26,6 +26,8 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -149,10 +151,10 @@
}
/** Our internal application list */
- private List<App> mApps = new ArrayList<App>();
+ private List<App> mApps = Collections.synchronizedList(new ArrayList<App>());
/** Internal map to keep track of logging information by app name */
- HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
+ Map<Integer, AppScanStats> mAppScanStats = new ConcurrentHashMap<Integer, AppScanStats>();
/** Internal list of connected devices **/
Set<Connection> mConnections = new HashSet<Connection>();
@@ -251,7 +253,6 @@
Connection connection = i.next();
if (connection.connId == connId) {
i.remove();
- break;
}
}
}
diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java
index 232477b..cc6ab3b 100644
--- a/src/com/android/bluetooth/gatt/GattDebugUtils.java
+++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -73,6 +73,10 @@
return false;
}
+ if (intent == null) {
+ Log.d(TAG, "received NULL intent in GattService. Return.");
+ return true;
+ }
String action = intent.getAction();
Log.d(TAG, "handleDebugAction() action=" + action);
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index fd8551a..7040307 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -172,6 +172,7 @@
private AppOpsManager mAppOps;
private static GattService sGattService;
+ private boolean mNativeAvailable;
/**
* Reliable write queue
@@ -179,6 +180,8 @@
private Set<String> mReliableQueue = new HashSet<String>();
static {
+ if (DBG) Log.d(TAG, "classInitNative called");
+ System.loadLibrary("bluetooth_jni");
classInitNative();
}
@@ -193,6 +196,7 @@
Log.d(TAG, "start()");
}
initializeNative();
+ mNativeAvailable = true;
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAppOps = getSystemService(AppOpsManager.class);
mAdvertiseManager = new AdvertiseManager(this, AdapterService.getAdapterService());
@@ -219,14 +223,18 @@
mServerMap.clear();
mHandleMap.clear();
mReliableQueue.clear();
- if (mAdvertiseManager != null) {
- mAdvertiseManager.cleanup();
- }
- if (mScanManager != null) {
- mScanManager.cleanup();
- }
- if (mPeriodicScanManager != null) {
- mPeriodicScanManager.cleanup();
+ if (mNativeAvailable) {
+ mNativeAvailable = false;
+ cleanupNative();
+ if (mAdvertiseManager != null) {
+ mAdvertiseManager.cleanup();
+ }
+ if (mScanManager != null) {
+ mScanManager.cleanup();
+ }
+ if (mPeriodicScanManager != null) {
+ mPeriodicScanManager.cleanup();
+ }
}
return true;
}
@@ -236,15 +244,19 @@
if (DBG) {
Log.d(TAG, "cleanup()");
}
- cleanupNative();
- if (mAdvertiseManager != null) {
- mAdvertiseManager.cleanup();
- }
- if (mScanManager != null) {
- mScanManager.cleanup();
- }
- if (mPeriodicScanManager != null) {
- mPeriodicScanManager.cleanup();
+
+ if (mNativeAvailable) {
+ mNativeAvailable = false;
+ cleanupNative();
+ if (mAdvertiseManager != null) {
+ mAdvertiseManager.cleanup();
+ }
+ if (mScanManager != null) {
+ mScanManager.cleanup();
+ }
+ if (mPeriodicScanManager != null) {
+ mPeriodicScanManager.cleanup();
+ }
}
}
@@ -2012,8 +2024,9 @@
if (app != null) {
app.recordScanStop(client.scannerId);
}
-
- mScanManager.stopScan(client);
+ if (mScanManager != null) {
+ mScanManager.stopScan(client);
+ }
}
void stopScan(PendingIntent intent, String callingPackage) {
@@ -2046,6 +2059,20 @@
}
}
+ boolean isScanClient(int clientIf) {
+ for (ScanClient client : mScanManager.getRegularScanQueue()) {
+ if (client.scannerId == clientIf) {
+ return true;
+ }
+ }
+ for (ScanClient client : mScanManager.getBatchScanQueue()) {
+ if (client.scannerId == clientIf) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void unregAll() {
for (Integer appId : mClientMap.getAllAppsIds()) {
if (DBG) {
@@ -2053,6 +2080,25 @@
}
unregisterClient(appId);
}
+ for (Integer appId : mServerMap.getAllAppsIds()) {
+ if (DBG) Log.d(TAG, "unreg:" + appId);
+ unregisterServer(appId);
+ }
+ for (Integer appId : mScannerMap.getAllAppsIds()) {
+ if (DBG) Log.d(TAG, "unreg:" + appId);
+ if (isScanClient(appId)) {
+ ScanClient client = new ScanClient(appId);
+ stopScan(client);
+ unregisterScanner(appId);
+ }
+ }
+ if (mAdvertiseManager != null) {
+ mAdvertiseManager.stopAdvertisingSets();
+ }
+ }
+
+ public void setAptXLowLatencyMode(boolean enabled){
+ mScanManager.setAptXLowLatencyMode(enabled);
}
/**************************************************************************
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 15233e4..6c47711 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -95,6 +95,15 @@
Entry entry = new Entry();
entry.type = TYPE_SOLICIT_UUID;
entry.uuid = uuid;
+ entry.uuid_mask = new UUID(0, 0);
+ mEntries.add(entry);
+ }
+
+ void addSolicitUuid(UUID uuid, UUID uuidMask) {
+ Entry entry = new Entry();
+ entry.type = TYPE_SOLICIT_UUID;
+ entry.uuid = uuid;
+ entry.uuid_mask = uuidMask;
mEntries.add(entry);
}
@@ -179,6 +188,14 @@
addUuid(filter.getServiceUuid().getUuid(), filter.getServiceUuidMask().getUuid());
}
}
+ if (filter.getServiceSolicitationUuid() != null) {
+ if (filter.getServiceSolicitationUuidMask() == null) {
+ addSolicitUuid(filter.getServiceSolicitationUuid().getUuid());
+ } else {
+ addSolicitUuid(filter.getServiceSolicitationUuid().getUuid(),
+ filter.getServiceSolicitationUuidMask().getUuid());
+ }
+ }
if (filter.getManufacturerData() != null) {
if (filter.getManufacturerDataMask() == null) {
addManufacturerData(filter.getManufacturerId(), filter.getManufacturerData());
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 5207c69..926837b 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -20,6 +20,7 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
@@ -49,6 +50,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -77,13 +79,19 @@
private static final int MSG_SUSPEND_SCANS = 4;
private static final int MSG_RESUME_SCANS = 5;
private static final int MSG_IMPORTANCE_CHANGE = 6;
+ /*For suspending both unfiltered & filtered scans*/
+ private static final int MSG_SUSPEND_SCAN_ALL = 7;
+ /* To handle display changed events in Handler thread context to avoid ANR */
+ private static final int MSG_HANDLE_DISPLAY_CHANGED = 8;
private static final String ACTION_REFRESH_BATCHED_SCAN =
"com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
+
// Timeout for each controller operation.
private static final int OPERATION_TIME_OUT_MILLIS = 500;
- private int mLastConfiguredScanSetting = Integer.MIN_VALUE;
+ private int mLastConfiguredScanSettingLE1M = Integer.MIN_VALUE;
+ private int mLastConfiguredScanSettingLECoded = Integer.MIN_VALUE;
// Scan parameters for batch scan.
private BatchScanParams mBatchScanParms;
@@ -91,6 +99,7 @@
private GattService mService;
private BroadcastReceiver mBatchAlarmReceiver;
private boolean mBatchAlarmReceiverRegistered;
+ private boolean mIsAptXLowLatencyModeEnabled;
private ScanNative mScanNative;
private volatile ClientHandler mHandler;
@@ -117,6 +126,18 @@
}
}
+ private class PhyInfo {
+ public int scanPhy;
+ public int scanModeLE1M;
+ public int scanModeLECoded;
+
+ PhyInfo(int scanPhy, int scanModeLE1M, int scanModeLECoded) {
+ this.scanPhy = scanPhy;
+ this.scanModeLE1M = scanModeLE1M;
+ this.scanModeLECoded = scanModeLECoded;
+ }
+ };
+
ScanManager(GattService service) {
mRegularScanClients =
Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
@@ -267,6 +288,7 @@
@Override
public void handleMessage(Message msg) {
+ Log.i(TAG, "msg.what = " + msg.what);
switch (msg.what) {
case MSG_START_BLE_SCAN:
handleStartScan((ScanClient) msg.obj);
@@ -289,6 +311,12 @@
case MSG_IMPORTANCE_CHANGE:
handleImportanceChange((UidImportance) msg.obj);
break;
+ case MSG_SUSPEND_SCAN_ALL:
+ handleSuspendScanAll();
+ break;
+ case MSG_HANDLE_DISPLAY_CHANGED:
+ handleScanOnDisplayChanged();
+ break;
default:
// Shouldn't happen.
Log.e(TAG, "received an unkown message : " + msg.what);
@@ -333,13 +361,28 @@
return;
}
+ if (isAptXLowLatencyModeEnabled()) {
+ Log.i(TAG, "Cannot start Scan when aptX LL mode is enabled. This scan will be"
+ + " resumed when aptX LL mode is disabled: " + client.scannerId);
+ mSuspendedScanClients.add(client);
+ if (client.stats != null) {
+ client.stats.recordScanSuspend(client.scannerId);
+ }
+ return;
+ }
+
// Begin scan operations.
if (isBatchClient(client)) {
mBatchClients.add(client);
mScanNative.startBatchScan(client);
} else {
mRegularScanClients.add(client);
- mScanNative.startRegularScan(client);
+ boolean ret = mScanNative.startRegularScan(client);
+ if (!ret) {
+ mRegularScanClients.remove(client);
+ return;
+ }
+
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
@@ -355,6 +398,8 @@
void handleStopScan(ScanClient client) {
Utils.enforceAdminPermission(mService);
+ boolean appDied;
+ int scannerId;
if (client == null) {
return;
}
@@ -363,6 +408,22 @@
mSuspendedScanClients.remove(client);
}
+ // The caller may pass a dummy client with only clientIf
+ // and appDied status. Perform the operation on the
+ // actual client in that case.
+ appDied = client.appDied;
+ scannerId = client.scannerId;
+ client = mScanNative.getRegularScanClient(scannerId);
+ if (client == null) {
+ if (DBG) Log.d(TAG, "regular client is null");
+ client = mScanNative.getBatchScanClient(scannerId);
+
+ if(client == null) {
+ if (DBG) Log.d(TAG,"batch client is null");
+ return;
+ }
+ }
+
if (mRegularScanClients.contains(client)) {
mScanNative.stopRegularScan(client);
@@ -373,10 +434,10 @@
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
}
- } else {
+ } else if (mBatchClients.contains(client)) {
mScanNative.stopBatchScan(client);
}
- if (client.appDied) {
+ if (appDied) {
if (DBG) {
Log.d(TAG, "app died, unregister scanner - " + client.scannerId);
}
@@ -426,6 +487,18 @@
}
}
}
+ void handleSuspendScanAll() {
+ for (ScanClient client : mRegularScanClients) {
+ if (!mScanNative.isOpportunisticScanClient(client)) {
+ /*Suspend both unfiltered & filtered scans*/
+ if (client.stats != null) {
+ client.stats.recordScanSuspend(client.scannerId);
+ }
+ handleStopScan(client);
+ mSuspendedScanClients.add(client);
+ }
+ }
+ }
void handleResumeScans() {
for (ScanClient client : mSuspendedScanClients) {
@@ -477,6 +550,7 @@
private static final int DELIVERY_MODE_IMMEDIATE = 0;
private static final int DELIVERY_MODE_ON_FOUND_LOST = 1;
private static final int DELIVERY_MODE_BATCH = 2;
+ private static final int DELIVERY_MODE_ROUTE = 8;
private static final int ONFOUND_SIGHTINGS_AGGRESSIVE = 1;
private static final int ONFOUND_SIGHTINGS_STICKY = 4;
@@ -535,6 +609,7 @@
mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE);
Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+ batchIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
@@ -548,9 +623,13 @@
if (mBatchClients.isEmpty()) {
return;
}
- // Note this actually flushes all pending batch data.
- if (mBatchClients.iterator().hasNext()) {
- flushBatchScanResults(mBatchClients.iterator().next());
+ try {
+ // Note this actually flushes all pending batch data.
+ if (mBatchClients.iterator().hasNext()) {
+ flushBatchScanResults(mBatchClients.iterator().next());
+ }
+ } catch (NoSuchElementException e) {
+ Log.e(TAG, "Element not found: " + e);
}
}
}
@@ -576,40 +655,94 @@
if (DBG) {
Log.d(TAG, "configureRegularScanParams() - queue=" + mRegularScanClients.size());
}
- int curScanSetting = Integer.MIN_VALUE;
+ int curScanSettingLE1M = Integer.MIN_VALUE;
+ int curScanSettingLECoded = Integer.MIN_VALUE;
+ int scanPhy = BluetoothDevice.PHY_LE_1M;
ScanClient client = getAggressiveClient(mRegularScanClients);
- if (client != null) {
- curScanSetting = client.settings.getScanMode();
+ PhyInfo phyInfoResult = getPhyInfo(mRegularScanClients);
+ boolean scanModeChanged = false;
+ int scanWindowLE1M = Integer.MIN_VALUE;
+ int scanIntervalLE1M = Integer.MIN_VALUE;
+ int scanWindowLECoded = Integer.MIN_VALUE;
+ int scanIntervalLECoded = Integer.MIN_VALUE;
+ int[] scanInterval = new int[2];
+ int[] scanWindow = new int[2];
+ int phyCnt = 0;
+
+
+ if (phyInfoResult != null) {
+ curScanSettingLE1M = phyInfoResult.scanModeLE1M;
+ curScanSettingLECoded = phyInfoResult.scanModeLECoded;
+ scanPhy = phyInfoResult.scanPhy;
}
if (DBG) {
- Log.d(TAG, "configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting
- + " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting);
+ Log.d(TAG, "configureRegularScanParams() - ScanSetting LE 1M Scan mode=" + curScanSettingLE1M
+ + "ScanSetting LE Coded Scan mode=" + curScanSettingLECoded
+ + " mLastConfiguredScanSettingLE1M=" + mLastConfiguredScanSettingLE1M
+ + " mLastConfiguredScanSettingLECoded=" + mLastConfiguredScanSettingLECoded
+ + "scanPhy=" + scanPhy);
}
- if (curScanSetting != Integer.MIN_VALUE
- && curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
- if (curScanSetting != mLastConfiguredScanSetting) {
- int scanWindow = getScanWindowMillis(client.settings);
- int scanInterval = getScanIntervalMillis(client.settings);
+ /* Check whether scan mode for LE 1M PHY has changed and compute the appropriate
+ * scan interval and scan window values
+ */
+ if ((curScanSettingLE1M != Integer.MIN_VALUE
+ && curScanSettingLE1M != ScanSettings.SCAN_MODE_OPPORTUNISTIC)) {
+ if ((curScanSettingLE1M != mLastConfiguredScanSettingLE1M) ||
+ (curScanSettingLECoded != mLastConfiguredScanSettingLECoded)) {
+ scanModeChanged = true;
+ ScanSettings settings = new ScanSettings.Builder().setScanMode(curScanSettingLE1M).build();
+ scanWindowLE1M = getScanWindowMillis(settings);
+ scanIntervalLE1M = getScanIntervalMillis(settings);
// convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
- scanWindow = Utils.millsToUnit(scanWindow);
- scanInterval = Utils.millsToUnit(scanInterval);
- gattClientScanNative(false);
- if (DBG) {
- Log.d(TAG, "configureRegularScanParams - scanInterval = " + scanInterval
- + "configureRegularScanParams - scanWindow = " + scanWindow);
- }
- gattSetScanParametersNative(client.scannerId, scanInterval, scanWindow);
- gattClientScanNative(true);
- mLastConfiguredScanSetting = curScanSetting;
+ scanWindow[phyCnt] = Utils.millsToUnit(scanWindowLE1M);
+ scanInterval[phyCnt] = Utils.millsToUnit(scanIntervalLE1M);
+ phyCnt++;
}
- } else {
- mLastConfiguredScanSetting = curScanSetting;
+ }
+ else {
+ mLastConfiguredScanSettingLE1M = curScanSettingLE1M;
if (DBG) {
Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
}
}
+
+ /* Check whether scan mode for LE Coded PHY has changed and compute the appropriate
+ * scan interval and scan window values
+ */
+ if((curScanSettingLECoded != Integer.MIN_VALUE
+ && curScanSettingLECoded != ScanSettings.SCAN_MODE_OPPORTUNISTIC)) {
+ if ((curScanSettingLECoded != mLastConfiguredScanSettingLECoded) ||
+ (curScanSettingLE1M != mLastConfiguredScanSettingLE1M)) {
+ scanModeChanged = true;
+ ScanSettings settings = new ScanSettings.Builder().setScanMode(curScanSettingLECoded).build();
+ scanWindowLECoded = getScanWindowMillis(settings);
+ scanIntervalLECoded = getScanIntervalMillis(settings);
+ // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
+ scanWindow[phyCnt] = Utils.millsToUnit(scanWindowLECoded);
+ scanInterval[phyCnt] = Utils.millsToUnit(scanIntervalLECoded);
+ }
+ }
+ else {
+ mLastConfiguredScanSettingLECoded = curScanSettingLECoded;
+ if (DBG) {
+ Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
+ }
+ }
+ if(scanModeChanged) {
+ gattClientScanNative(false);
+ if (DBG) {
+ Log.d(TAG, "configureRegularScanParams - scanInterval LE 1M = " + scanIntervalLE1M
+ + "configureRegularScanParams - scanWindow LE 1M= " + scanWindowLE1M
+ + "configureRegularScanParams - scanInterval LE Coded= " + scanIntervalLECoded
+ + "configureRegularScanParams - scanWindow LE Coded= " + scanWindowLECoded);
+ }
+ gattSetScanParametersNative(client.scannerId, scanPhy, scanInterval, scanWindow);
+ gattClientScanNative(true);
+ mLastConfiguredScanSettingLE1M = curScanSettingLE1M;
+ mLastConfiguredScanSettingLECoded = curScanSettingLECoded;
+ }
}
ScanClient getAggressiveClient(Set<ScanClient> cList) {
@@ -626,11 +759,46 @@
return result;
}
- void startRegularScan(ScanClient client) {
+ PhyInfo getPhyInfo(Set<ScanClient> cList) {
+ PhyInfo result = null;
+ int curScanSettingLE1M = Integer.MIN_VALUE;
+ int curScanSettingLECoded = Integer.MIN_VALUE;
+ int curScanPhy = BluetoothDevice.PHY_LE_1M;
+ int aggregateScanPhy = BluetoothDevice.PHY_LE_1M;
+ for (ScanClient client : cList) {
+ // Get the most aggresive scan mode for each PHY
+ curScanPhy = client.settings.getPhy();
+ if (((curScanPhy & BluetoothDevice.PHY_LE_1M)== BluetoothDevice.PHY_LE_1M) &&
+ (client.settings.getScanMode() > curScanSettingLE1M)) {
+ curScanSettingLE1M = client.settings.getScanMode();
+ }
+ if (((curScanPhy & BluetoothDevice.PHY_LE_CODED)== BluetoothDevice.PHY_LE_CODED) &&
+ (client.settings.getScanMode() > curScanSettingLECoded)) {
+ curScanSettingLECoded = client.settings.getScanMode();
+ }
+ aggregateScanPhy |= client.settings.getPhy();
+ }
+ result = new PhyInfo(aggregateScanPhy, curScanSettingLE1M, curScanSettingLECoded);
+ return result;
+ }
+
+ boolean startRegularScan(ScanClient client) {
if (isFilteringSupported() && mFilterIndexStack.isEmpty()
&& mClientFilterIndexMap.isEmpty()) {
initFilterIndexStack();
}
+
+ if (isRoutingScanClient(client) && (client.filters.size() > mFilterIndexStack.size())) {
+ try {
+ mService.onScanManagerErrorCallback(client.scannerId,
+ ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
+ Log.e(TAG, "startRegularScan, routing scan out of HARDWARE_RESOURCES");
+ } catch (RemoteException e) {
+ Log.e(TAG, "startRegularScan, routing scan failed on onScanManagerCallback", e);
+ }
+ return false;
+ }
+
if (isFilteringSupported()) {
configureScanFilters(client);
}
@@ -638,6 +806,7 @@
if (numRegularScanClients() == 1) {
gattClientScanNative(true);
}
+ return true;
}
private int numRegularScanClients() {
@@ -664,7 +833,7 @@
private boolean isExemptFromScanDowngrade(ScanClient client) {
return isOpportunisticScanClient(client) || isFirstMatchScanClient(client)
- || !shouldUseAllPassFilter(client);
+ || !shouldUseAllPassFilter(client) || isRoutingScanClient(client);
}
private boolean isOpportunisticScanClient(ScanClient client) {
@@ -676,6 +845,10 @@
!= 0;
}
+ private boolean isRoutingScanClient(ScanClient client) {
+ return client.settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_SENSOR_ROUTING;
+ }
+
private void resetBatchScan(ScanClient client) {
int scannerId = client.scannerId;
BatchScanParams batchScanParams = getBatchScanParams();
@@ -948,12 +1121,15 @@
for (ScanFilter filter : client.filters) {
ScanFilterQueue queue = new ScanFilterQueue();
queue.addScanFilter(filter);
+ ScanFilterQueue.Entry[] entries = queue.toArray();
int featureSelection = queue.getFeatureSelection();
int filterIndex = mFilterIndexStack.pop();
- resetCountDownLatch();
- gattClientScanFilterAddNative(scannerId, queue.toArray(), filterIndex);
- waitForCallback();
+ if (entries != null && entries.length > 0) {
+ resetCountDownLatch();
+ gattClientScanFilterAddNative(scannerId, entries, filterIndex);
+ waitForCallback();
+ }
resetCountDownLatch();
if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
@@ -1055,6 +1231,9 @@
if (client == null) {
return true;
}
+ if (isRoutingScanClient(client)) {
+ return false;
+ }
if (client.filters == null || client.filters.isEmpty()) {
return true;
}
@@ -1085,7 +1264,7 @@
onLostTimeout = 10000;
if (DBG) {
Log.d(TAG, "configureFilterParamter " + onFoundTimeout + " " + onLostTimeout + " "
- + onFoundCount + " " + numOfTrackingEntries);
+ + onFoundCount + " " + numOfTrackingEntries + ", deliveryMode=" + deliveryMode);
}
FilterParams filtValue =
new FilterParams(scannerId, filterIndex, featureSelection, LIST_LOGIC_TYPE,
@@ -1107,6 +1286,9 @@
|| (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
return DELIVERY_MODE_ON_FOUND_LOST;
}
+ if (isRoutingScanClient(client)) {
+ return DELIVERY_MODE_ROUTE;
+ }
return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE
: DELIVERY_MODE_BATCH;
}
@@ -1266,8 +1448,8 @@
private native void gattClientScanNative(boolean start);
- private native void gattSetScanParametersNative(int clientIf, int scanInterval,
- int scanWindow);
+ private native void gattSetScanParametersNative(int clientIf, int scan_phy, int[] scanInterval,
+ int[] scanWindow);
/************************** Filter related native methods ********************************/
private native void gattClientScanFilterAddNative(int clientId,
@@ -1313,6 +1495,23 @@
return false;
}
+ public boolean isAptXLowLatencyModeEnabled() {
+ Log.d(TAG, "isAptXLowLatencyModeEnabled: " + mIsAptXLowLatencyModeEnabled);
+ return mIsAptXLowLatencyModeEnabled;
+ }
+
+ public void setAptXLowLatencyMode(boolean enabled){
+ Log.d(TAG, "setAptXLowLatencyMode: mIsAptXLowLatencyModeEnabled: "
+ + mIsAptXLowLatencyModeEnabled + "enabled: " + enabled);
+ mIsAptXLowLatencyModeEnabled = enabled;
+ if (mIsAptXLowLatencyModeEnabled) {
+ sendMessage(MSG_SUSPEND_SCAN_ALL, null);
+ } else {
+ sendMessage(MSG_RESUME_SCANS, null);
+ }
+ }
+
+
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@Override
@@ -1323,11 +1522,7 @@
@Override
public void onDisplayChanged(int displayId) {
- if (isScreenOn() && mLocationManager.isLocationEnabled()) {
- sendMessage(MSG_RESUME_SCANS, null);
- } else {
- sendMessage(MSG_SUSPEND_SCANS, null);
- }
+ sendMessage(MSG_HANDLE_DISPLAY_CHANGED, null);
}
};
@@ -1400,4 +1595,12 @@
mScanNative.configureRegularScanParams();
}
}
+
+ private void handleScanOnDisplayChanged() {
+ if (isScreenOn() && mLocationManager.isLocationEnabled()) {
+ sendMessage(MSG_RESUME_SCANS, null);
+ } else {
+ sendMessage(MSG_SUSPEND_SCANS, null);
+ }
+ }
}
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
index 6a4af04..b60b0d6 100644
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ b/src/com/android/bluetooth/hdp/HealthService.java
@@ -221,8 +221,13 @@
break;
case MESSAGE_UNREGISTER_APPLICATION: {
BluetoothHealthAppConfiguration appConfig =
- (BluetoothHealthAppConfiguration) msg.obj;
- int appId = (mApps.get(appConfig)).mAppId;
+ (BluetoothHealthAppConfiguration) msg.obj;
+ AppInfo appInfo = mApps.get(appConfig);
+ if (appInfo == null) {
+ Log.e(TAG, "No AppInfo found for AppConfig: " + appConfig);
+ break;
+ }
+ int appId = appInfo.mAppId;
if (!unregisterHealthAppNative(appId)) {
Log.e(TAG, "Failed to unregister application: id: " + appId);
callStatusCallback(appConfig,
@@ -233,7 +238,12 @@
case MESSAGE_CONNECT_CHANNEL: {
HealthChannel chan = (HealthChannel) msg.obj;
byte[] devAddr = Utils.getByteAddress(chan.mDevice);
- int appId = (mApps.get(chan.mConfig)).mAppId;
+ AppInfo appInfo = mApps.get(chan.mConfig);
+ if (appInfo == null) {
+ Log.e(TAG, "No AppInfo found for AppConfig: " + chan.mConfig);
+ break;
+ }
+ int appId = appInfo.mAppId;
chan.mChannelId = connectChannelNative(devAddr, appId);
if (chan.mChannelId == -1) {
callHealthChannelCallback(chan.mConfig, chan.mDevice,
@@ -273,6 +283,10 @@
|| regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
//unlink to death once app is unregistered
AppInfo appInfo = mApps.get(appConfig);
+ if (appInfo == null){
+ Log.e(TAG, "No AppInfo found for AppConfig " + appConfig);
+ break;
+ }
appInfo.cleanup();
mApps.remove(appConfig);
}
@@ -285,9 +299,9 @@
findAppConfigByAppId(channelStateEvent.mAppId);
int newState;
newState = convertHalChannelState(channelStateEvent.mState);
- if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED
- && appConfig == null) {
- Log.e(TAG, "Disconnected for non existing app");
+ if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED ||
+ appConfig == null) {
+ Log.e(TAG,"Disconnected for non existing app");
break;
}
if (chan == null) {
@@ -588,9 +602,15 @@
if (VDBG) {
Log.d(TAG, "Health Device Application: " + config + " State Change: status:" + status);
}
- IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+ AppInfo appInfo = mApps.get(config);
+ if (appInfo == null) {
+ Log.e(TAG, " No AppInfo found for AppConfig " + config);
+ return;
+ }
+ IBluetoothHealthCallback callback = appInfo.mCallback;
if (callback == null) {
Log.e(TAG, "Callback object null");
+ return;
}
try {
@@ -680,8 +700,12 @@
Log.e(TAG, "Exception while duping: " + e);
}
}
-
- IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+ AppInfo appInfo = mApps.get(config);
+ if (appInfo == null) {
+ Log.e(TAG, "No AppInfo found for AppConfig " + config);
+ return;
+ }
+ IBluetoothHealthCallback callback = appInfo.mCallback;
if (callback == null) {
Log.e(TAG, "No callback found for config: " + config);
return;
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index b30eb62..d1c99be 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -40,6 +40,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -764,6 +765,7 @@
int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
connectionStateChanged(device, fromState, toState);
+
}
}
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 937e767..6e4650d 100644
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -23,8 +23,10 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.Contacts;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
@@ -62,9 +64,27 @@
* BT periphals don't. Limit the number we'll report. */
private static final int MAX_PHONEBOOK_SIZE = 16384;
+ private final String SIM_URI = "content://icc/adn";
+
+ static final String[] SIM_PROJECTION = new String[] {
+ Contacts.DISPLAY_NAME,
+ CommonDataKinds.Phone.NUMBER,
+ CommonDataKinds.Phone.TYPE,
+ CommonDataKinds.Phone.LABEL
+ };
+
+ private static final int NAME_COLUMN_INDEX = 0;
+ private static final int NUMBER_COLUMN_INDEX = 1;
+ private static final int NUMBERTYPE_COLUMN_INDEX = 2;
+
private static final String OUTGOING_CALL_WHERE = Calls.TYPE + "=" + Calls.OUTGOING_TYPE;
private static final String INCOMING_CALL_WHERE = Calls.TYPE + "=" + Calls.INCOMING_TYPE;
private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
+ private static final String VISIBLE_PHONEBOOK_WHERE = null;
+ private static final String VISIBLE_SIM_PHONEBOOK_WHERE = null;
+
+ public static final int OUTGOING_IMS_TYPE = 1001;
+ public static final int OUTGOING_WIFI_TYPE = 1004;
private class PhonebookResult {
public Cursor cursor; // result set of last query
@@ -88,7 +108,7 @@
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private final HashMap<String, PhonebookResult> mPhonebooks =
- new HashMap<String, PhonebookResult>(4);
+ new HashMap<String, PhonebookResult>(5);
static final int TYPE_UNKNOWN = -1;
static final int TYPE_READ = 0;
@@ -104,6 +124,8 @@
mPhonebooks.put("RC", new PhonebookResult()); // received calls
mPhonebooks.put("MC", new PhonebookResult()); // missed calls
mPhonebooks.put("ME", new PhonebookResult()); // mobile phonebook
+ mPhonebooks.put("SM", new PhonebookResult()); // SIM phonebook
+
mCurrentPhonebook = "ME"; // default to mobile phonebook
mCpbrIndex1 = mCpbrIndex2 = -1;
}
@@ -115,24 +137,32 @@
/** Returns the last dialled number, or null if no numbers have been called */
public String getLastDialledNumber() {
String[] projection = {Calls.NUMBER};
- Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
- Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null,
- Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
- if (cursor == null) {
- Log.w(TAG, "getLastDialledNumber, cursor is null");
- return null;
- }
+ try {
+ Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
+ Calls.TYPE + " = " + Calls.OUTGOING_TYPE + " OR " + Calls.TYPE +
+ " = " + OUTGOING_IMS_TYPE + " OR " + Calls.TYPE + " = " +
+ OUTGOING_WIFI_TYPE, null, Calls.DEFAULT_SORT_ORDER +
+ " LIMIT 1");
+ log("Queried the last dialled number for CS, IMS, WIFI calls");
+ if (cursor == null) {
+ Log.w(TAG, "getLastDialledNumber, cursor is null");
+ return null;
+ }
- if (cursor.getCount() < 1) {
+ if (cursor.getCount() < 1) {
+ cursor.close();
+ Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
+ return null;
+ }
+ cursor.moveToNext();
+ int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
+ String number = cursor.getString(column);
cursor.close();
- Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
- return null;
+ return number;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while querying last dialled number", e);
}
- cursor.moveToNext();
- int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
- String number = cursor.getString(column);
- cursor.close();
- return number;
+ return null;
}
public boolean getCheckingAccessPermission() {
@@ -206,11 +236,6 @@
case TYPE_READ: // Read
log("handleCpbsCommand - read command");
// Return current size and max size
- if ("SM".equals(mCurrentPhonebook)) {
- atCommandResponse = "+CPBS: \"SM\",0," + getMaxPhoneBookSize(0);
- atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
- break;
- }
PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true);
if (pbr == null) {
atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
@@ -279,21 +304,17 @@
*/
log("handleCpbrCommand - test command");
int size;
- if ("SM".equals(mCurrentPhonebook)) {
- size = 0;
- } else {
- PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
- if (pbr == null) {
- atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
- mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
- atCommandErrorCode);
- break;
- }
- size = pbr.cursor.getCount();
- log("handleCpbrCommand - size = " + size);
- pbr.cursor.close();
- pbr.cursor = null;
+ PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
+ if (pbr == null) {
+ atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+ mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+ atCommandErrorCode);
+ break;
}
+ size = pbr.cursor.getCount();
+ log("handleCpbrCommand - size = "+size);
+ pbr.cursor.close();
+ pbr.cursor = null;
if (size == 0) {
/* Sending "+CPBR: (1-0)" can confused some carkits, send "1-1" * instead */
size = 1;
@@ -310,6 +331,7 @@
// AT+CPBR=<index1>[,<index2>]
log("handleCpbrCommand - set/read command");
if (mCpbrIndex1 != -1) {
+ Log.i(TAG, "mCpbrIndex1 :" + mCpbrIndex1);
/* handling a CPBR at the moment, reject this CPBR command */
atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
@@ -349,7 +371,9 @@
mCheckingAccessPermission = true;
int permission = checkAccessPermission(remoteDevice);
+ Log.i(TAG, "permission :" + permission);
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ Log.i(TAG, "permission to access is granted:" );
mCheckingAccessPermission = false;
atCommandResult = processCpbrCommand(remoteDevice);
mCpbrIndex1 = mCpbrIndex2 = -1;
@@ -357,6 +381,7 @@
atCommandErrorCode);
break;
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ Log.i(TAG, "permission to access is not granted:" );
mCheckingAccessPermission = false;
mCpbrIndex1 = mCpbrIndex2 = -1;
mNativeInterface.atResponseCode(remoteDevice,
@@ -400,6 +425,7 @@
private synchronized boolean queryPhonebook(String pb, PhonebookResult pbr) {
String where;
boolean ancillaryPhonebook = true;
+ boolean simPhonebook = false;
if (pb.equals("ME")) {
ancillaryPhonebook = false;
@@ -410,6 +436,10 @@
where = INCOMING_CALL_WHERE;
} else if (pb.equals("MC")) {
where = MISSED_CALL_WHERE;
+ } else if (pb.equals("SM")) {
+ ancillaryPhonebook = false;
+ simPhonebook = true;
+ where = VISIBLE_SIM_PHONEBOOK_WHERE;
} else {
return false;
}
@@ -420,9 +450,14 @@
}
if (ancillaryPhonebook) {
- pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
- Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
- if (pbr.cursor == null) {
+ try {
+ pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION, where,
+ null, Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
+ if (pbr.cursor == null) {
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while querying the call log database", e);
return false;
}
@@ -432,17 +467,56 @@
pbr.typeColumn = -1;
pbr.nameColumn = -1;
} else {
- final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
- pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION, where, null,
- Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
- if (pbr.cursor == null) {
- return false;
- }
+ Log.i(TAG, "simPhonebook " + simPhonebook);
+ if (simPhonebook) {
+ final Uri mysimUri = Uri.parse(SIM_URI);
+ try {
+ pbr.cursor = mContentResolver.query(mysimUri, SIM_PROJECTION,
+ where, null, null);
+ Log.i(TAG, "querySIMcontactbook where " + where + " uri :" + mysimUri);
+ if (pbr.cursor == null) {
+ Log.i(TAG, "querying phone contacts on sim returned null.");
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while querying sim contact book database", e);
+ return false;
+ }
- pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
- pbr.numberPresentationColumn = -1;
- pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
- pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
+ pbr.numberColumn = NUMBER_COLUMN_INDEX;
+ pbr.numberPresentationColumn = -1;
+ pbr.typeColumn = NUMBERTYPE_COLUMN_INDEX;
+ pbr.nameColumn = NAME_COLUMN_INDEX;
+ Log.i(TAG, " pbr.numberColumn: " + pbr.numberColumn +
+ " pbr.numberPresentationColumn: " + pbr.numberPresentationColumn +
+ " pbr.typeColumn: " + pbr.typeColumn +
+ " pbr.nameColumn: " + pbr.nameColumn);
+ } else {
+ final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
+ try {
+ pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION,
+ where, null, Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
+ Log.i(TAG, "queryPhonebook where " + where + " uri :" + phoneContentUri);
+ if (pbr.cursor == null) {
+ Log.i(TAG, "querying phone contacts on memory returned null.");
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while querying phone contacts on memory", e);
+ return false;
+ }
+
+ Log.i(TAG, "Phone.NUMBER: " + Phone.NUMBER + " Phone.TYPE :" + Phone.TYPE +
+ "Phone.DISPLAY_NAME :" + Phone.DISPLAY_NAME);
+ pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
+ pbr.numberPresentationColumn = -1;
+ pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
+ pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
+ Log.i(TAG, " pbr.numberColumn: " + pbr.numberColumn +
+ " pbr.numberPresentationColumn: " + pbr.numberPresentationColumn +
+ " pbr.typeColumn: " + pbr.typeColumn +
+ " pbr.nameColumn: " + pbr.nameColumn);
+ }
}
Log.i(TAG, "Refreshed phonebook " + pb + " with " + pbr.cursor.getCount() + " results");
return true;
@@ -485,12 +559,6 @@
StringBuilder response = new StringBuilder();
String record;
- // Shortcut SM phonebook
- if ("SM".equals(mCurrentPhonebook)) {
- atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
- return atCommandResult;
- }
-
// Check phonebook
PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
if (pbr == null) {
@@ -528,18 +596,24 @@
// try caller id lookup
// TODO: This code is horribly inefficient. I saw it
// take 7 seconds to process 100 missed calls.
- Cursor c = mContentResolver.query(
- Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number),
- new String[]{
+ try {
+ Cursor c = mContentResolver.query(
+ Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+ number), new String[]{
PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE
- }, null, null, null);
- if (c != null) {
- if (c.moveToFirst()) {
- name = c.getString(0);
- type = c.getInt(1);
+ }, null, null, null);
+ if (c != null) {
+ if (c.moveToFirst()) {
+ name = c.getString(0);
+ type = c.getInt(1);
+ }
+ c.close();
}
- c.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while querying phonebook database", e);
+ return HeadsetHalConstants.AT_RESPONSE_ERROR;
}
+
if (DBG && name == null) {
log("Caller ID lookup failed for " + number);
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
new file mode 100644
index 0000000..1b46dd7
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.bluetooth.hfp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Defines methods used for synchronization between HFP and A2DP
+ */
+public class HeadsetA2dpSync {
+ private static final String TAG = HeadsetA2dpSync.class.getSimpleName();
+
+ // system inteface
+ private HeadsetSystemInterface mSystemInterface;
+ private HeadsetService mHeadsetService;
+ // Hash for storing the A2DP states
+ private ConcurrentHashMap<BluetoothDevice, Integer> mA2dpConnState =
+ new ConcurrentHashMap<BluetoothDevice, Integer>();
+
+ // internal variables.
+ private int mA2dpSuspendTriggered;// to keep track if A2dp was supended by HFP.
+ private BluetoothDevice mDummyDevice = null;
+
+ //reason for a2dp suspended.
+ public static final int A2DP_SUSPENDED_NOT_TRIGGERED = 0;
+ public static final int A2DP_SUSPENDED_BY_CS_CALL = 1;
+ public static final int A2DP_SUSPENDED_BY_VOIP_CALL = 2;
+ public static final int A2DP_SUSPENDED_BY_VR = 3;
+ // State for a2dp Device
+ // current implementation is only concerned about DISCONNECTED, CONNECTED and PLAYING.
+ public static final int A2DP_DISCONNECTED = 0;// this implies no connection
+ public static final int A2DP_CONNECTING = 1;
+ public static final int A2DP_CONNECTED = 2;// this implies connected but not playing
+ public static final int A2DP_DISCONNECTING = 3;
+ public static final int A2DP_PLAYING = 4;// this implies connected and PLaying
+ public static final int A2DP_SUSPENDED = 5;
+
+ HeadsetA2dpSync(HeadsetSystemInterface systemInterface,HeadsetService service) {
+ mSystemInterface = systemInterface;
+ mHeadsetService = service;
+ mA2dpSuspendTriggered = A2DP_SUSPENDED_NOT_TRIGGERED;// initialize with not suspended.
+ String dummyAddress = "AA:BB:CC:DD:EE:00";
+ mDummyDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(dummyAddress);
+ }
+
+ /* check if any of the a2dp device is playing
+ * TODO: Remove if not required.
+ */
+ /* check if any device is in PLAYING/CONNECTED state
+ * return types:
+ * A2DP_DISCONNECTED: NO Device COnnected
+ * A2DP_CONNECTED: All Devices are in Connected state, none in playing state
+ * A2DP_PLAYING: Atlease one device in playing state
+ */
+ public int isA2dpPlaying() {
+ int a2dpState = A2DP_DISCONNECTED;
+ for(Integer value: mA2dpConnState.values()) {
+ if(value == A2DP_CONNECTED) {
+ a2dpState = value;
+ }
+ if(value == A2DP_PLAYING) {
+ a2dpState = value;
+ Log.d(TAG," isA2dpPlaying returns = " + a2dpState);
+ return a2dpState;
+ }
+ }
+ Log.d(TAG," isA2dpPlaying returns = " + a2dpState);
+ return a2dpState;
+ }
+ /* check and suspend A2DP
+ * return: true in case we have to wait for suepnd confirmation
+ * false in case we don't have to wait for suepnd confirmation
+ * If A2DP is connected, call A2dpSuspended=true ( so that A2DP can't start while call
+ * is acvtive), but we don't have to wait for suspend confirmation in this case
+ * If A2DP is playing, call A2dpSuspended=true, and wait for suspend confirm
+ * if A2DP is not connected, don't do anything.
+ * caller need to send reason for suspend request( VR/CS-CALL/VOIP-CALL)
+ */
+ public boolean suspendA2DP(int reason, BluetoothDevice device){
+ int a2dpState = isA2dpPlaying();
+ Log.d(TAG," suspendA2DP currPlayingState = "+ a2dpState + " for reason " + reason
+ + "mA2dpSuspendTriggered = " + mA2dpSuspendTriggered + " for device " + device);
+ if (mA2dpSuspendTriggered != A2DP_SUSPENDED_NOT_TRIGGERED) {
+ // A2DPSuspend was triggered already, don't need to do anything.
+ if(a2dpState == A2DP_PLAYING) {
+ // we are still waiting for suspend from a2dp.Caller shld wait
+ return true;
+ } else {
+ // not playing, caller need not wait.
+ return false;
+ }
+ }
+ // not device in connected state, nothing to do. Caller shld not wait
+ if(a2dpState == A2DP_DISCONNECTED) {
+ Log.d(TAG," A2DP not Connected, nothing to do ");
+ return false;
+ }
+ if(a2dpState == A2DP_CONNECTED) {
+ mA2dpSuspendTriggered = reason;
+ mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+ Log.d(TAG," A2DP Connected,don't wait for suspend ");
+ return false;
+ }
+ if(a2dpState == A2DP_PLAYING) {
+ mA2dpSuspendTriggered = reason;
+ mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+ Log.d(TAG," A2DP Playing ,wait for suspend ");
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ *This api will be called by SMs, to make A2dpSuspended=false
+ */
+ public boolean releaseA2DP(BluetoothDevice device) {
+ Log.d(TAG," releaseA2DP mA2dpSuspendTriggered " + mA2dpSuspendTriggered +
+ " by device " + device);
+ if(mA2dpSuspendTriggered == A2DP_SUSPENDED_NOT_TRIGGERED) {
+ return true;
+ }
+ if (mHeadsetService.isInCall() || mHeadsetService.isRinging() ||
+ mHeadsetService.isAudioOn()) {
+ Log.d(TAG," Call/Ring/SCO on for some other stateMachine, bail out ");
+ return true;
+ }
+ // A2DP Suspend was triggered by HFP.
+ mA2dpSuspendTriggered = A2DP_SUSPENDED_NOT_TRIGGERED;
+ mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+ return true;
+ }
+
+ public void updateA2DPPlayingState(Intent intent) {
+ int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothA2dp.STATE_NOT_PLAYING);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+ BluetoothA2dp.STATE_NOT_PLAYING);
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ Log.d(TAG," updateA2DPPlayingState device: " + device + " transition " +
+ prevState + "->" + currState);
+
+ // if device is not there and state=DISCONNECTED, bail out
+ if(!mA2dpConnState.containsKey(device)) {
+ Log.e(TAG," Got PLay_UPdate without Connectoin Update, this shld not happen "+ device);
+ }
+ switch(currState) {
+ case BluetoothA2dp.STATE_NOT_PLAYING:
+ mA2dpConnState.put(device, A2DP_CONNECTED);
+ /*
+ * send message to statemachine. We send message to SMs
+ * only when all devices moved to SUSPENDED.
+ */
+ int a2dpState = isA2dpPlaying();
+ if ((a2dpState == A2DP_DISCONNECTED) ||
+ (a2dpState == A2DP_CONNECTED)) {
+ mHeadsetService.sendA2dpStateChangeUpdate(a2dpState);
+ }
+ break;
+ case BluetoothA2dp.STATE_PLAYING:
+ mA2dpConnState.put(device, A2DP_PLAYING);
+ // if call/ ring is ongoing and there is HFP connected device and we received playing,
+ // we need to suspend
+ if ((mHeadsetService.isInCall() || mHeadsetService.isRinging()) &&
+ mHeadsetService.getConnectedDevices().size() != 0) {
+ Log.d(TAG," CALL/Ring is active ");
+ suspendA2DP(A2DP_SUSPENDED_BY_CS_CALL, mDummyDevice);
+ }
+ break;
+ }
+ Log.d(TAG," device: " + device + " state = " + mA2dpConnState.get(device));
+ }
+ /*
+ * BookKeeping of A2dp Device State, based on intents from A2DPStateMachine.
+ */
+ public void updateA2DPConnectionState(Intent intent) {
+ int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ Log.d(TAG," updateA2DPConnectionState device: " + device + " transition " +
+ prevState + "->" + currState);
+
+ // if device is not there and state=DISCONNECTED, bail out
+ if(!mA2dpConnState.containsKey(device) &&
+ (currState == BluetoothProfile.STATE_DISCONNECTED)) {
+ Log.e(TAG," Got Disc for device not in entry "+ device);
+ return;
+ }
+ switch(currState) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ mA2dpConnState.remove(device);
+ break;
+ // treating everything else as Connected
+ case BluetoothProfile.STATE_DISCONNECTING:
+ case BluetoothProfile.STATE_CONNECTING:
+ mA2dpConnState.put(device, A2DP_CONNECTED);
+ break;
+ case BluetoothProfile.STATE_CONNECTED:
+ mA2dpConnState.put(device, A2DP_CONNECTED);
+ if (mHeadsetService.isInCall() || mHeadsetService.isRinging()) {
+ Log.d(TAG," CALL is active/ringing, A2DP got connected, suspending");
+ suspendA2DP(A2DP_SUSPENDED_BY_CS_CALL, mDummyDevice);
+ }
+ break;
+ }
+ Log.d(TAG," device: " + device + " state = " + mA2dpConnState.get(device));
+ }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index bcf123c..93012e2 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -33,11 +33,11 @@
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.PhoneConstants;
import java.util.HashMap;
import java.util.Objects;
-
/**
* Class that manages Telephony states
*
@@ -72,6 +72,18 @@
private int mCindRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
// HFP 1.6 CIND battchg value
private int mCindBatteryCharge;
+ // Current Call Number
+ private String mCindNumber;
+ //Current Phone Number Type
+ private int mType = 0;
+ // if its a CS call
+ private boolean mIsCsCall = true;
+ // SIM is absent
+ private int SIM_ABSENT = 0;
+ // SIM is present
+ private int SIM_PRESENT = 1;
+ // Array to keep the SIM status
+ private int[] mSimStatus = {0, 0};
private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
private PhoneStateListener mPhoneStateListener;
@@ -91,6 +103,25 @@
mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
headsetService.getStateMachinesThreadLooper());
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ IntentFilter simStateChangedFilter =
+ new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ //Record the SIM states upon BT reset
+ try {
+ for (int slotIndex = 0; slotIndex < mSimStatus.length; slotIndex++) {
+ if (mTelephonyManager.getSimState(slotIndex) ==
+ TelephonyManager.SIM_STATE_READY) {
+ Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is present");
+ mSimStatus[slotIndex] = SIM_PRESENT;
+ } else {
+ Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is absent");
+ mSimStatus[slotIndex] = SIM_ABSENT;
+ }
+ }
+ mHeadsetService.registerReceiver(mPhoneStateChangeReceiver, simStateChangedFilter);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to register phone state change receiver and unable to get" +
+ " sim states", e);
+ }
}
/**
@@ -102,6 +133,11 @@
stopListenForPhoneState();
}
mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ try {
+ mHeadsetService.unregisterReceiver(mPhoneStateChangeReceiver);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to unregister phone state change receiver", e);
+ }
}
@Override
@@ -160,13 +196,22 @@
return;
}
Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
- mPhoneStateListener = new HeadsetPhoneStateListener(subId,
- mHeadsetService.getStateMachinesThreadLooper());
- mTelephonyManager.listen(mPhoneStateListener, events);
- if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
- mTelephonyManager.setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+ if (mTelephonyManager == null) {
+ Log.e(TAG, "mTelephonyManager is null, "
+ + "cannot start listening for phone state changes");
+ } else {
+ mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+ mHeadsetService.getStateMachinesThreadLooper());
+ try {
+ mTelephonyManager.listen(mPhoneStateListener, events);
+ if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+ mTelephonyManager.setRadioIndicationUpdateMode(
+ TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+ TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Exception while registering for signal strength notifications", e);
+ }
}
}
@@ -177,13 +222,61 @@
}
Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
+ getTelephonyEventsToListen());
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- mTelephonyManager.setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+ if (mTelephonyManager == null) {
+ Log.e(TAG, "mTelephonyManager is null, "
+ + "cannot send request to stop listening or update radio to normal state");
+ } else {
+ try {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mTelephonyManager.setRadioIndicationUpdateMode(
+ TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+ TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+ } catch (Exception e) {
+ Log.w(TAG, "exception while registering for signal strength notifications", e);
+ }
+ }
mPhoneStateListener = null;
}
+ private final BroadcastReceiver mPhoneStateChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive: " + intent.getAction());
+ final String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+ if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+ // This is a sticky broadcast, so if it's already been loaded,
+ // this'll execute immediately.
+ if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)) {
+ final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+ SubscriptionManager.getDefaultVoicePhoneId());
+ Log.d(TAG, "SIM loaded, making mIsSimStateLoaded to true for slotId = "
+ + slotId);
+ mSimStatus[slotId] = SIM_PRESENT;
+ mIsSimStateLoaded = true;
+ sendDeviceStateChanged();
+ } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)
+ || IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(stateExtra)
+ || IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+ SubscriptionManager.getDefaultVoicePhoneId());
+ Log.d(TAG, "SIM unloaded, making mIsSimStateLoaded to false for slotId = "
+ + slotId);
+ mSimStatus[slotId] = SIM_ABSENT;
+ mIsSimStateLoaded = false;
+ for (int i = 0; i < mSimStatus.length; i++) {
+ if (mSimStatus[i] == SIM_PRESENT) {
+ Log.d(TAG, "SIM is loaded in either of the slots, making" +
+ " mIsSimStateLoaded to true");
+ mIsSimStateLoaded = true;
+ break;
+ }
+ }
+ sendDeviceStateChanged();
+ }
+ }
+ }
+ };
+
int getCindService() {
return mCindService;
}
@@ -197,6 +290,16 @@
mNumActive = numActive;
}
+ boolean getIsCsCall() {
+ Log.d(TAG, "In getIsCsCall, mIsCsCall: " + mIsCsCall);
+ return mIsCsCall;
+ }
+
+ void setIsCsCall(boolean isCsCall) {
+ Log.d(TAG, "In setIsCsCall, mIsCsCall: " + isCsCall);
+ mIsCsCall = isCsCall;
+ }
+
int getCallState() {
return mCallState;
}
@@ -219,6 +322,22 @@
return mCindSignal;
}
+ void setNumber(String mNumberCall ) {
+ mCindNumber = mNumberCall;
+ }
+
+ String getNumber() {
+ return mCindNumber;
+ }
+
+ void setType(int mTypeCall) {
+ mType = mTypeCall;
+ }
+
+ int getType() {
+ return mType;
+ }
+
int getCindRoam() {
return mCindRoam;
}
@@ -251,7 +370,7 @@
// use the service indicator, but only the signal indicator
int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
- Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
+ Log.d(TAG, "sendDeviceStateChanged. mService=" + service + " mIsSimStateLoaded="
+ mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
+ " mBatteryCharge=" + mCindBatteryCharge);
mHeadsetService.onDeviceStateChanged(
@@ -280,7 +399,9 @@
@Override
public synchronized void onServiceStateChanged(ServiceState serviceState) {
+ Log.d(TAG, "Enter onServiceStateChanged");
mServiceState = serviceState;
+ int prevService = mCindService;
int cindService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE)
? HeadsetHalConstants.NETWORK_STATE_AVAILABLE
: HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
@@ -297,34 +418,28 @@
// If this is due to a SIM insertion, we want to defer sending device state changed
// until all the SIM config is loaded.
if (cindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
- mIsSimStateLoaded = false;
sendDeviceStateChanged();
- return;
}
- IntentFilter simStateChangedFilter =
- new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- mHeadsetService.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
- // This is a sticky broadcast, so if it's already been loaded,
- // this'll execute immediately.
- if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
- intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
- mIsSimStateLoaded = true;
- sendDeviceStateChanged();
- mHeadsetService.unregisterReceiver(this);
- }
- }
- }
- }, simStateChangedFilter);
+ //Update the signal strength when the service state changes
+ if (prevService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE &&
+ cindService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE &&
+ mCindSignal == 0) {
+ Log.d(TAG, "Service is available and signal strength was zero, updating the "+
+ "current signal strength");
+ mCindSignal = mTelephonyManager.getSignalStrength().getLevel() + 1;
+ // +CIND "signal" indicator is always between 0 to 5
+ mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
+ sendDeviceStateChanged();
+ }
+ Log.d(TAG, "Exit onServiceStateChanged");
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
int prevSignal = mCindSignal;
if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+ Log.d(TAG, "Service is not available, signal strength is set to zero");
mCindSignal = 0;
} else {
mCindSignal = signalStrength.getLevel() + 1;
@@ -333,6 +448,7 @@
mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
// This results in a lot of duplicate messages, hence this check
if (prevSignal != mCindSignal) {
+ Log.d(TAG, "Updating the signal strength change to the apps");
sendDeviceStateChanged();
}
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 2ccc1e4..440e414 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -19,7 +19,10 @@
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import android.annotation.Nullable;
+import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
+
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
@@ -56,6 +59,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import android.telecom.TelecomManager;
+
/**
* Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application.
@@ -85,7 +90,7 @@
*/
public class HeadsetService extends ProfileService {
private static final String TAG = "HeadsetService";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String DISABLE_INBAND_RINGING_PROPERTY =
"persist.bluetooth.disableinbandringing";
private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.Handsfree};
@@ -94,6 +99,7 @@
private static final int DIALING_OUT_TIMEOUT_MS = 10000;
private int mMaxHeadsetConnections = 1;
+ private int mSetMaxConfig;
private BluetoothDevice mActiveDevice;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
@@ -101,6 +107,7 @@
private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
private HeadsetNativeInterface mNativeInterface;
private HeadsetSystemInterface mSystemInterface;
+ private HeadsetA2dpSync mHfpA2dpSyncInterface;
private boolean mAudioRouteAllowed = true;
// Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions
private boolean mForceScoAudio;
@@ -116,6 +123,8 @@
private boolean mStarted;
private boolean mCreated;
private static HeadsetService sHeadsetService;
+ private boolean mDisconnectAll;
+ private boolean mIsTwsPlusEnabled = false;
@Override
public IProfileServiceBinder initBinder() {
@@ -134,8 +143,9 @@
@Override
protected boolean start() {
Log.i(TAG, "start()");
- if (mStarted) {
- throw new IllegalStateException("start() called twice");
+ if (sHeadsetService != null) {
+ Log.w(TAG, "HeadsetService is already running");
+ return true;
}
// Step 1: Get adapter service, should never be null
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
@@ -147,7 +157,31 @@
mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
mSystemInterface.init();
// Step 4: Initialize native interface
- mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
+ mSetMaxConfig = mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
+ if(mAdapterService.isVendorIntfEnabled()) {
+ String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+ if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
+ mIsTwsPlusEnabled = true;
+ }
+ Log.i(TAG, "mIsTwsPlusEnabled: " + mIsTwsPlusEnabled);
+ if (mIsTwsPlusEnabled){
+ //set MaxConn to 2 if TWSPLUS enabled
+ mMaxHeadsetConnections = 2;
+ }
+ if (mMaxHeadsetConnections > 2) {
+ //If the set config is more than 2
+ //limit it to 2
+ mMaxHeadsetConnections = 2;
+ mSetMaxConfig = 2;
+ }
+ //Only if the User set config 1 and TWS+ is enabled leaves
+ //these maxConn at 2 and setMaxConfig to 1. this is to avoid
+ //connecting to more than 1 legacy device even though max conns
+ //is set 2 because of TWS+ requirement
+ Log.d(TAG, "Max_HFP_Connections " + mMaxHeadsetConnections);
+ Log.d(TAG, "mSetMaxConfig " + mSetMaxConfig);
+ }
+
mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
// Add 1 to allow a pending device to be connecting or disconnecting
mNativeInterface.init(mMaxHeadsetConnections + 1, isInbandRingingEnabled());
@@ -163,10 +197,17 @@
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
+ filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(TelecomManager.ACTION_CALL_TYPE);
registerReceiver(mHeadsetReceiver, filter);
// Step 7: Mark service as started
+
setHeadsetService(this);
mStarted = true;
+
+ mHfpA2dpSyncInterface = new HeadsetA2dpSync(mSystemInterface, this);
+ Log.i(TAG, " HeadsetService Started ");
return true;
}
@@ -190,16 +231,30 @@
mInbandRingingRuntimeDisable = false;
mForceScoAudio = false;
mAudioRouteAllowed = true;
- mMaxHeadsetConnections = 1;
+ if(mAdapterService.isVendorIntfEnabled()) {
+ //to enable TWS
+ if (mIsTwsPlusEnabled) {
+ mMaxHeadsetConnections = 2;
+ } else {
+ mMaxHeadsetConnections = 1;
+ }
+ } else {
+ mMaxHeadsetConnections = 1;
+ }
mVoiceRecognitionStarted = false;
mVirtualCallStarted = false;
if (mDialingOutTimeoutEvent != null) {
- mStateMachinesThread.getThreadHandler().removeCallbacks(mDialingOutTimeoutEvent);
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
+ .removeCallbacks(mDialingOutTimeoutEvent);
+ }
mDialingOutTimeoutEvent = null;
}
if (mVoiceRecognitionTimeoutEvent != null) {
- mStateMachinesThread.getThreadHandler()
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
.removeCallbacks(mVoiceRecognitionTimeoutEvent);
+ }
mVoiceRecognitionTimeoutEvent = null;
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -215,11 +270,15 @@
mNativeInterface.cleanup();
// Step 3: Destroy system interface
mSystemInterface.stop();
- // Step 2: Stop handler thread
- mStateMachinesThread.quitSafely();
- mStateMachinesThread = null;
- // Step 1: Clear
- mAdapterService = null;
+ synchronized (mStateMachines) {
+ // Step 2: Stop handler thread
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.quitSafely();
+ }
+ mStateMachinesThread = null;
+ // Step 1: Clear
+ mAdapterService = null;
+ }
return true;
}
@@ -249,13 +308,30 @@
*/
@VisibleForTesting
public Looper getStateMachinesThreadLooper() {
- return mStateMachinesThread.getLooper();
+ if (mStateMachinesThread != null) {
+ return mStateMachinesThread.getLooper();
+ }
+ return null;
}
interface StateMachineTask {
void execute(HeadsetStateMachine stateMachine);
}
+ // send message to all statemachines in connecting and connected state.
+ private void doForEachConnectedConnectingStateMachine(StateMachineTask task) {
+ synchronized (mStateMachines) {
+ for (BluetoothDevice device :
+ getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES)) {
+ HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine == null) {
+ return;
+ }
+ task.execute(stateMachine);
+ }
+ }
+ }
+
private boolean doForStateMachine(BluetoothDevice device, StateMachineTask task) {
synchronized (mStateMachines) {
HeadsetStateMachine stateMachine = mStateMachines.get(device);
@@ -270,15 +346,21 @@
private void doForEachConnectedStateMachine(StateMachineTask task) {
synchronized (mStateMachines) {
for (BluetoothDevice device : getConnectedDevices()) {
- task.execute(mStateMachines.get(device));
+ HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine == null) {
+ continue;
+ }
+ task.execute(stateMachine);
}
}
}
void onDeviceStateChanged(HeadsetDeviceState deviceState) {
- doForEachConnectedStateMachine(
+ synchronized (mStateMachines) {
+ doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
deviceState));
+ }
}
/**
@@ -298,11 +380,15 @@
case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
// Create new state machine if none is found
if (stateMachine == null) {
- stateMachine = HeadsetObjectsFactory.getInstance()
+ if (mStateMachinesThread != null) {
+ stateMachine = HeadsetObjectsFactory.getInstance()
.makeStateMachine(stackEvent.device,
mStateMachinesThread.getLooper(), this, mAdapterService,
mNativeInterface, mSystemInterface);
- mStateMachines.put(stackEvent.device, stateMachine);
+ mStateMachines.put(stackEvent.device, stateMachine);
+ } else {
+ Log.w(TAG, "messageFromNative: mStateMachinesThread is null");
+ }
}
break;
}
@@ -340,8 +426,10 @@
case AudioManager.VOLUME_CHANGED_ACTION: {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
- doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
+ synchronized (mStateMachines) {
+ doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent));
+ }
}
break;
}
@@ -388,6 +476,23 @@
}
break;
}
+ case BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED: {
+ logD("Received BluetoothA2dp Play State changed");
+ mHfpA2dpSyncInterface.updateA2DPPlayingState(intent);
+ break;
+ }
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: {
+ logD("Received BluetoothA2dp Connection State changed");
+ mHfpA2dpSyncInterface.updateA2DPConnectionState(intent);
+ break;
+ }
+ case TelecomManager.ACTION_CALL_TYPE: {
+ logD("Received BluetoothHeadset action call type");
+ synchronized (mStateMachines) {
+ doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
+ HeadsetStateMachine.UPDATE_CALL_TYPE, intent));
+ }
+ }
default:
Log.w(TAG, "Unknown action " + action);
}
@@ -463,6 +568,15 @@
return service.getDevicesMatchingConnectionStates(states);
}
+ public List<BluetoothDevice> getAllDevicesMatchingConnectionStates(int[] states) {
+ HeadsetService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getAllDevicesMatchingConnectionStates(states);
+ }
+
+
@Override
public int getConnectionState(BluetoothDevice device) {
HeadsetService service = getService();
@@ -675,6 +789,83 @@
sHeadsetService = instance;
}
+ public BluetoothDevice getTwsPlusConnectedPeer(BluetoothDevice device) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (!adapterService.isTwsPlusDevice(device)) {
+ logD("getTwsPlusConnectedPeer: Not a TWS+ device");
+ return null;
+ }
+ List<BluetoothDevice> connDevices = getConnectedDevices();
+
+ int size = connDevices.size();
+ for(int i = 0; i < size; i++) {
+ BluetoothDevice ConnectedDevice = connDevices.get(i);
+ if (adapterService.getTwsPlusPeerAddress(device).equals(ConnectedDevice.getAddress())) {
+ return ConnectedDevice;
+ }
+ }
+ return null;
+ }
+
+ private boolean isConnectionAllowed(BluetoothDevice device,
+ List<BluetoothDevice> connDevices
+ ) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ boolean allowSecondHfConnection = false;
+
+ if (!mIsTwsPlusEnabled && adapterService.isTwsPlusDevice(device)) {
+ logD("No TWSPLUS connections as It is not Enabled");
+ return false;
+ }
+
+ if (connDevices.size() == 0) {
+ allowSecondHfConnection = true;
+ } else {
+ BluetoothDevice connectedDev = connDevices.get(0);
+ if (adapterService.isTwsPlusDevice(connectedDev)) {
+ //If connected device is TWSPlus device
+ //Allow connection only if the outgoing is peer of TWS connected earbud
+ if (adapterService.isTwsPlusDevice(device)&&
+ adapterService.getTwsPlusPeerAddress(device).equals(connectedDev.getAddress())) {
+ allowSecondHfConnection = true;
+ } else {
+ allowSecondHfConnection = false;
+ if (connDevices.size() == 1) {
+ mDisconnectAll = true;
+ }
+ }
+ } else {
+ //if Connected device is not TWS
+ if (adapterService.isTwsPlusDevice(device)) {
+ //outgoing connection is TWSP
+ allowSecondHfConnection = false;
+ if (connDevices.size() == 1) {
+ //only if connected devices is 1
+ //disconnect it and override it with tws+
+ mDisconnectAll = true;
+ }
+ } else {
+ //Outgoing connection is legacy device
+ //For legacy case use the config set by User
+ if (connDevices.size() < mSetMaxConfig) {
+ allowSecondHfConnection = true;
+ } else {
+ allowSecondHfConnection = false;
+ }
+ }
+ }
+ Log.v(TAG, "isTwsPlusDevice for " + device +
+ "is"+ adapterService.isTwsPlusDevice(device));
+ Log.v(TAG, "TWS Peer Addr: " +
+ adapterService.getTwsPlusPeerAddress(device));
+ Log.v(TAG, "Connected device" + connectedDev.getAddress());
+ }
+
+ Log.v(TAG, "allowSecondHfConnection: " + allowSecondHfConnection);
+ Log.v(TAG, "DisconnectAll: " + mDisconnectAll);
+ return allowSecondHfConnection;
+ }
+
public boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
@@ -691,10 +882,14 @@
Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());
HeadsetStateMachine stateMachine = mStateMachines.get(device);
if (stateMachine == null) {
- stateMachine = HeadsetObjectsFactory.getInstance()
+ if (mStateMachinesThread != null) {
+ stateMachine = HeadsetObjectsFactory.getInstance()
.makeStateMachine(device, mStateMachinesThread.getLooper(), this,
mAdapterService, mNativeInterface, mSystemInterface);
- mStateMachines.put(device, stateMachine);
+ mStateMachines.put(device, stateMachine);
+ } else {
+ Log.w(TAG, "connect: mStateMachinesThread is null");
+ }
}
int connectionState = stateMachine.getConnectionState();
if (connectionState == BluetoothProfile.STATE_CONNECTED
@@ -704,11 +899,24 @@
return false;
}
List<BluetoothDevice> connectingConnectedDevices =
- getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ addAllDevicesPendingRetryConnect(connectingConnectedDevices);
boolean disconnectExisting = false;
- if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+ mDisconnectAll = false;
+ if (connectingConnectedDevices.size() == 0) {
+ Log.e(TAG, "No Connected devices!");
+ }
+ if (!isConnectionAllowed(device, connectingConnectedDevices)) {
// When there is maximum one device, we automatically disconnect the current one
if (mMaxHeadsetConnections == 1) {
+ if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
+ Log.w(TAG, "Connection attemp to TWS+ when not enabled, Rejecting it");
+ return false;
+ } else {
+ disconnectExisting = true;
+ }
+ } else if (mDisconnectAll) {
+ //In Dual HF case
disconnectExisting = true;
} else {
Log.w(TAG, "Max connection has reached, rejecting connection to " + device);
@@ -726,7 +934,7 @@
return true;
}
- boolean disconnect(BluetoothDevice device) {
+ public boolean disconnect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
synchronized (mStateMachines) {
@@ -747,6 +955,18 @@
return true;
}
+ public boolean isInCall() {
+ boolean isCallOngoing = mSystemInterface.isInCall();
+ Log.d(TAG," isInCall " + isCallOngoing);
+ return isCallOngoing;
+ }
+
+ public boolean isRinging() {
+ boolean isRingOngoing = mSystemInterface.isRinging();
+ Log.d(TAG," isRinging " + isRingOngoing);
+ return isRingOngoing;
+ }
+
public List<BluetoothDevice> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
ArrayList<BluetoothDevice> devices = new ArrayList<>();
@@ -760,6 +980,54 @@
return devices;
}
+ private void addAllDevicesPendingRetryConnect(List<BluetoothDevice> devices) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Log.d(TAG, " add all devices pending retry connect");
+ synchronized (mStateMachines) {
+ for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+ BluetoothDevice device = stateMachine.getDevice();
+ if ((stateMachine.isPendingRetryConnect() == true) &&
+ (!devices.contains(device))) {
+ devices.add(device);
+ Log.d(TAG, " add pending retry connect device: " + device);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to get all devices with matching connection state
+ *
+ */
+ private List<BluetoothDevice> getAllDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ ArrayList<BluetoothDevice> devices = new ArrayList<>();
+ if (states == null) {
+ Log.e(TAG, "->States is null");
+ return devices;
+ }
+ final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+ if (bondedDevices == null) {
+ Log.e(TAG, "->Bonded device is null");
+ return devices;
+ }
+ synchronized (mStateMachines) {
+ for (BluetoothDevice device : bondedDevices) {
+
+ int connectionState = getConnectionState(device);
+ Log.e(TAG, "Connec state for: " + device + "is" + connectionState);
+ for (int state : states) {
+ if (connectionState == state) {
+ devices.add(device);
+ Log.e(TAG, "Adding device: " + device);
+ break;
+ }
+ }
+ }
+ }
+ return devices;
+ }
+
/**
* Same as the API method {@link BluetoothHeadset#getDevicesMatchingConnectionStates(int[])}
*
@@ -779,10 +1047,7 @@
}
synchronized (mStateMachines) {
for (BluetoothDevice device : bondedDevices) {
- final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
- if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
- continue;
- }
+
int connectionState = getConnectionState(device);
for (int state : states) {
if (connectionState == state) {
@@ -863,15 +1128,20 @@
+ ", fall back to requesting device");
device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
}
- mStateMachinesThread.getThreadHandler()
- .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
+ .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+ } else {
+ Log.w(TAG, "startVoiceRecognition: mStateMachinesThread is null");
+ }
mVoiceRecognitionTimeoutEvent = null;
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().release();
}
pendingRequestByHeadset = true;
}
- if (!Objects.equals(device, mActiveDevice) && !setActiveDevice(device)) {
+ if (!Objects.equals(device, mActiveDevice) &&
+ !mAdapterService.isTwsPlusDevice(device) && !setActiveDevice(device)) {
Log.w(TAG, "startVoiceRecognition: failed to set " + device + " as active");
return false;
}
@@ -893,7 +1163,6 @@
} else {
stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device);
}
- stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
}
return true;
}
@@ -929,11 +1198,24 @@
return true;
}
- boolean isAudioOn() {
+ public boolean isAudioOn() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getNonIdleAudioDevices().size() > 0;
+ int numConnectedAudioDevices = getNonIdleAudioDevices().size();
+ Log.d(TAG," isAudioOn: The number of audio connected devices "
+ + numConnectedAudioDevices);
+ return numConnectedAudioDevices > 0;
}
+ public boolean isScoOrCallActive() {
+ Log.d(TAG, "isScoOrCallActive(): Call Active:" + mSystemInterface.isInCall() +
+ "Call is Ringing:" + mSystemInterface.isInCall() +
+ "SCO is Active:" + isAudioOn());
+ if (mSystemInterface.isInCall() || (mSystemInterface.isRinging()) || isAudioOn()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
boolean isAudioConnected(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
synchronized (mStateMachines) {
@@ -1035,6 +1317,9 @@
Log.w(TAG, "setActiveDevice: disconnectAudio failed on " + mActiveDevice);
}
}
+ if (!mNativeInterface.setActiveDevice(null)) {
+ Log.w(TAG, "setActiveDevice: Cannot set active device as null in native layer");
+ }
mActiveDevice = null;
broadcastActiveDevice(null);
return true;
@@ -1048,6 +1333,14 @@
+ " as active, device is not connected");
return false;
}
+ if (mActiveDevice != null && mAdapterService.isTwsPlusDevice(device) &&
+ mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+ !Objects.equals(device, mActiveDevice) &&
+ getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
+ Log.d(TAG,"Ignore setActiveDevice request");
+ return false;
+ }
+
if (!mNativeInterface.setActiveDevice(device)) {
Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer");
return false;
@@ -1064,13 +1357,16 @@
}
broadcastActiveDevice(mActiveDevice);
} else if (shouldPersistAudio()) {
- broadcastActiveDevice(mActiveDevice);
- if (!connectAudio(mActiveDevice)) {
- Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
- mActiveDevice = previousActiveDevice;
- mNativeInterface.setActiveDevice(previousActiveDevice);
- return false;
+ boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
+ if (!isPts) {
+ if (!connectAudio(mActiveDevice)) {
+ Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
+ mActiveDevice = previousActiveDevice;
+ mNativeInterface.setActiveDevice(previousActiveDevice);
+ return false;
+ }
}
+ broadcastActiveDevice(mActiveDevice);
} else {
broadcastActiveDevice(mActiveDevice);
}
@@ -1124,9 +1420,12 @@
return true;
}
if (isAudioOn()) {
- Log.w(TAG, "connectAudio: audio is not idle, current audio devices are "
- + Arrays.toString(getNonIdleAudioDevices().toArray()));
- return false;
+ //SCO is connecting or connected.
+ //Return true to telephony
+ Log.w(TAG, "connectAudio: audio is not idle, current audio devices are: "
+ + Arrays.toString(getNonIdleAudioDevices().toArray()) +
+ " ,returning true");
+ return true;
}
stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
}
@@ -1188,6 +1487,13 @@
}
}
+ boolean isVRStarted() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ synchronized (mStateMachines) {
+ return mVoiceRecognitionStarted;
+ }
+ }
+
private boolean startScoUsingVirtualVoiceCall() {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
@@ -1220,6 +1526,7 @@
return false;
}
mVirtualCallStarted = true;
+ mSystemInterface.getHeadsetPhoneState().setIsCsCall(false);
// Send virtual phone state changed to initialize SCO
phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, true);
phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, true);
@@ -1292,7 +1599,8 @@
return false;
}
}
- if (!setActiveDevice(fromDevice)) {
+ if (!mAdapterService.isTwsPlusDevice(fromDevice) &&
+ !setActiveDevice(fromDevice)) {
Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice);
return false;
}
@@ -1301,8 +1609,12 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
- mStateMachinesThread.getThreadHandler()
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
+ } else {
+ Log.w(TAG, "dialOutgoingCall: mStateMachinesThread is null");
+ }
return true;
}
}
@@ -1381,7 +1693,7 @@
+ ", already pending by " + mVoiceRecognitionTimeoutEvent);
return false;
}
- if (!setActiveDevice(fromDevice)) {
+ if (!mAdapterService.isTwsPlusDevice(fromDevice) && !setActiveDevice(fromDevice)) {
Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice
+ " as active");
return false;
@@ -1406,8 +1718,12 @@
return false;
}
mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
- mStateMachinesThread.getThreadHandler()
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
.postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
+ } else {
+ Log.w(TAG, "startVoiceRecognitionByHeadset: mStateMachinesThread is null");
+ }
if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
}
@@ -1432,8 +1748,12 @@
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().release();
}
- mStateMachinesThread.getThreadHandler()
+ if (mStateMachinesThread != null) {
+ mStateMachinesThread.getThreadHandler()
.removeCallbacks(mVoiceRecognitionTimeoutEvent);
+ } else {
+ Log.w(TAG, "stopVoiceRecognitionByHeadset: mStateMachinesThread is null");
+ }
mVoiceRecognitionTimeoutEvent = null;
}
if (mVoiceRecognitionStarted) {
@@ -1455,6 +1775,10 @@
int type, boolean isVirtualCall) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
synchronized (mStateMachines) {
+ if (mStateMachinesThread == null) {
+ Log.w(TAG, "mStateMachinesThread is null, returning");
+ return;
+ }
// Should stop all other audio mode in this case
if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) {
if (!isVirtualCall && mVirtualCallStarted) {
@@ -1465,6 +1789,12 @@
// stop voice recognition if there is any incoming call
stopVoiceRecognition(mActiveDevice);
}
+ } else {
+ // ignore CS non-call state update when virtual call started
+ if (!isVirtualCall && mVirtualCallStarted) {
+ Log.i(TAG, "Ignore CS non-call state update");
+ return;
+ }
}
if (mDialingOutTimeoutEvent != null) {
// Send result to state machine when dialing starts
@@ -1484,38 +1814,55 @@
}
}
}
- }
- mStateMachinesThread.getThreadHandler().post(() -> {
- boolean isCallIdleBefore = mSystemInterface.isCallIdle();
- mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
- mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
- mSystemInterface.getHeadsetPhoneState().setCallState(callState);
- // Suspend A2DP when call about is about to become active
- if (callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
- && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
- mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
- }
- });
- doForEachConnectedStateMachine(
- stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
+ mStateMachinesThread.getThreadHandler().post(() -> {
+ mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
+ mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
+ mSystemInterface.getHeadsetPhoneState().setCallState(callState);
+ });
+ List<BluetoothDevice> availableDevices =
+ getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ if(availableDevices.size() > 0) {
+ Log.i(TAG, "Update the phoneStateChanged status to connecting and " +
+ "connected devices");
+ doForEachConnectedConnectingStateMachine(
+ stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
new HeadsetCallState(numActive, numHeld, callState, number, type)));
- mStateMachinesThread.getThreadHandler().post(() -> {
- if (callState == HeadsetHalConstants.CALL_STATE_IDLE
- && mSystemInterface.isCallIdle() && !isAudioOn()) {
- // Resume A2DP when call ended and SCO is not connected
- mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+ mStateMachinesThread.getThreadHandler().post(() -> {
+ if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+ Log.i(TAG, "no call, sending resume A2DP message to state machines");
+ for (BluetoothDevice device : availableDevices) {
+ HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine == null) {
+ Log.w(TAG, "phoneStateChanged: device " + device +
+ " was never connected/connecting");
+ continue;
+ }
+ stateMachine.sendMessage(HeadsetStateMachine.RESUME_A2DP);
+ }
+ }
+ });
+ } else {
+ mStateMachinesThread.getThreadHandler().post(() -> {
+ if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+ //If no device is connected, resume A2DP if there is no call
+ Log.i(TAG, "No device is connected and no call, " +
+ "set A2DPsuspended to false");
+ mHfpA2dpSyncInterface.releaseA2DP(null);
+ }
+ });
}
- });
-
+ }
}
private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
String number, int type) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
- doForEachConnectedStateMachine(
+ synchronized (mStateMachines) {
+ doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
new HeadsetClccResponse(index, direction, status, mode, mpty, number,
type)));
+ }
}
private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
@@ -1544,9 +1891,12 @@
}
boolean isInbandRingingEnabled() {
+ boolean returnVal;
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
- DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
+ returnVal = BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
+ DISABLE_INBAND_RINGING_PROPERTY, true) && !mInbandRingingRuntimeDisable;
+ Log.d(TAG, "isInbandRingingEnabled returning: " + returnVal);
+ return returnVal;
}
/**
@@ -1565,7 +1915,7 @@
getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
if (fromState != BluetoothProfile.STATE_CONNECTED
&& toState == BluetoothProfile.STATE_CONNECTED) {
- if (audioConnectableDevices.size() > 1) {
+ if (audioConnectableDevices.size() > 1 && isInbandRingingEnabled()) {
mInbandRingingRuntimeDisable = true;
doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
@@ -1575,17 +1925,50 @@
}
if (fromState != BluetoothProfile.STATE_DISCONNECTED
&& toState == BluetoothProfile.STATE_DISCONNECTED) {
- if (audioConnectableDevices.size() <= 1) {
+ if (audioConnectableDevices.size() <= 1 ) {
mInbandRingingRuntimeDisable = false;
- doForEachConnectedStateMachine(
+ if(isInbandRingingEnabled()) {
+ doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
- 1));
+ 1));
+ }
}
if (device.equals(mActiveDevice)) {
- setActiveDevice(null);
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService.isTwsPlusDevice(device)) {
+ //if the disconnected device is a tws+ device
+ // and if peer device is connected, set the peer
+ // as an active device
+ BluetoothDevice peerDevice = getTwsPlusConnectedPeer(device);
+ if (peerDevice != null) {
+ setActiveDevice(peerDevice);
+ }
+ } else {
+ setActiveDevice(null);
+ }
}
}
}
+
+ // if active device is null, SLC connected, make this device as active.
+ if (fromState == BluetoothProfile.STATE_CONNECTING &&
+ toState == BluetoothProfile.STATE_CONNECTED &&
+ mActiveDevice == null) {
+ Log.i(TAG, "onConnectionStateChangedFromStateMachine: SLC connected, no active"
+ + " is present. Setting active device to " + device);
+ setActiveDevice(device);
+ }
+ }
+
+ public HeadsetA2dpSync getHfpA2DPSyncInterface(){
+ return mHfpA2dpSyncInterface;
+ }
+
+ public void sendA2dpStateChangeUpdate(int state) {
+ Log.d(TAG," sendA2dpStateChange newState = " + state);
+ doForEachConnectedConnectingStateMachine(
+ stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.A2DP_STATE_CHANGED,
+ state));
}
/**
@@ -1606,8 +1989,19 @@
}
private boolean shouldCallAudioBeActive() {
- return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
- && isInbandRingingEnabled());
+ boolean retVal = false;
+ // When the call is active/held, the call audio must be active
+ if (mSystemInterface.getHeadsetPhoneState().getNumActiveCall() > 0 ||
+ mSystemInterface.getHeadsetPhoneState().getNumHeldCall() > 0 ) {
+ Log.d(TAG, "shouldCallAudioBeActive(): returning true, since call is active/held");
+ return true;
+ }
+ // When call is in ringing state, SCO should not be accepted if
+ // in-band ringtone is not enabled
+ retVal = (mSystemInterface.isInCall() && !mSystemInterface.isRinging() )||
+ (mSystemInterface.isRinging() && isInbandRingingEnabled());
+ Log.d(TAG, "shouldCallAudioBeActive() returning " + retVal);
+ return retVal;
}
/**
@@ -1636,31 +2030,47 @@
int toState) {
synchronized (mStateMachines) {
if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- if (fromState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- if (mActiveDevice != null && !mActiveDevice.equals(device)
- && shouldPersistAudio()) {
- if (!connectAudio(mActiveDevice)) {
- Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
- + " audio to new " + "active device " + mActiveDevice
- + ", after " + device + " is disconnected from SCO");
- }
- }
- }
if (mVoiceRecognitionStarted) {
if (!stopVoiceRecognitionByHeadset(device)) {
Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice "
+ "recognition");
+ } else {
+ final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine != null) {
+ Log.d(TAG, "onAudioStateChangedFromStateMachine: send +bvra:0");
+ stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP,
+ device);
+ }
}
}
- if (mVirtualCallStarted) {
- if (!stopScoUsingVirtualVoiceCall()) {
- Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop virtual "
- + "voice call");
+
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ isAudioConnected(getTwsPlusConnectedPeer(device))) {
+ Log.w(TAG, "Ignore stop virtuall voice call if the other TWS+ device is "
+ + "audio connected");
+ } else {
+ if (mVirtualCallStarted) {
+ if (!stopScoUsingVirtualVoiceCall()) {
+ Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop "
+ + "virtual voice call");
+ }
}
}
- // Unsuspend A2DP when SCO connection is gone and call state is idle
- if (mSystemInterface.isCallIdle()) {
- mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+
+ //Transfer SCO is not needed for TWS+ devices
+ if (!mAdapterService.isTwsPlusDevice(device)) {
+ // trigger SCO after SCO disconnected with previous active
+ // device
+ if (mActiveDevice != null && !mActiveDevice.equals(device) &&
+ shouldPersistAudio()) {
+ Log.d(TAG, "onAudioStateChangedFromStateMachine: triggering SCO with device "
+ + mActiveDevice);
+ if (!connectAudio(mActiveDevice)) {
+ Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
+ + " audio to new " + "active device " + mActiveDevice
+ + ", after " + device + " is disconnected from SCO");
+ }
+ }
}
}
}
@@ -1683,41 +2093,44 @@
*/
public boolean okToAcceptConnection(BluetoothDevice device) {
// Check if this is an incoming connection in Quiet mode.
+ boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
if (mAdapterService.isQuietModeEnabled()) {
Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
return false;
}
- // Check priority and accept or reject the connection.
- // Note: Logic can be simplified, but keeping it this way for readability
- int priority = getPriority(device);
- int bondState = mAdapterService.getBondState(device);
- // If priority is undefined, it is likely that service discovery has not completed and peer
- // initiated the connection. Allow this connection only if the device is bonded or bonding
- boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED) && (
- bondState == BluetoothDevice.BOND_BONDING
- || bondState == BluetoothDevice.BOND_BONDED);
- // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
- boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
- || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) && (
- bondState == BluetoothDevice.BOND_BONDED
- || bondState == BluetoothDevice.BOND_BONDING);
- if (!serviceDiscoveryPending && !isEnabled) {
- // Otherwise, reject the connection if no service discovery is pending and priority is
- // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
- Log.w(TAG,
- "okToConnect: return false, priority=" + priority + ", bondState=" + bondState);
- return false;
+ if(!isPts) {
+ // Check priority and accept or reject the connection.
+ // Note: Logic can be simplified, but keeping it this way for readability
+ int priority = getPriority(device);
+ int bondState = mAdapterService.getBondState(device);
+ // If priority is undefined, it is likely that service discovery has not completed and peer
+ // initiated the connection. Allow this connection only if the device is bonded or bonding
+ boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED) && (
+ bondState == BluetoothDevice.BOND_BONDING
+ || bondState == BluetoothDevice.BOND_BONDED);
+ // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
+ boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
+ || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) && (
+ bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_BONDING);
+ if (!serviceDiscoveryPending && !isEnabled) {
+ // Otherwise, reject the connection if no service discovery is pending and priority is
+ // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
+ Log.w(TAG,
+ "okToConnect: return false, priority=" + priority + ", bondState=" + bondState);
+ return false;
+ }
}
List<BluetoothDevice> connectingConnectedDevices =
- getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
- if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+ getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+ addAllDevicesPendingRetryConnect(connectingConnectedDevices);
+ if (!isConnectionAllowed(device, connectingConnectedDevices)) {
Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections
+ " was reached, rejecting connection from " + device);
return false;
}
return true;
}
-
/**
* Checks if SCO should be connected at current system state
*
@@ -1726,10 +2139,13 @@
*/
public boolean isScoAcceptable(BluetoothDevice device) {
synchronized (mStateMachines) {
- if (device == null || !device.equals(mActiveDevice)) {
- Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
+ //allow 2nd eSCO from non-active tws+ earbud as well
+ if (!mAdapterService.isTwsPlusDevice(device)) {
+ if (device == null || !device.equals(mActiveDevice)) {
+ Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
+ " is not the current active device " + mActiveDevice);
- return false;
+ return false;
+ }
}
if (mForceScoAudio) {
return true;
@@ -1738,6 +2154,15 @@
Log.w(TAG, "isScoAcceptable: rejected SCO since audio route is not allowed");
return false;
}
+ /* if in-band ringtone is not enabled and if there is
+ no active/held/dialling/alerting call, return false */
+ if (isRinging() && !isInbandRingingEnabled() &&
+ !(mSystemInterface.getHeadsetPhoneState().getNumActiveCall() > 0 ||
+ mSystemInterface.getHeadsetPhoneState().getNumHeldCall() > 0 )) {
+ Log.w(TAG, "isScoAcceptable: rejected SCO since MT call in ringing," +
+ "in-band ringing not enabled");
+ return false;
+ }
if (mVoiceRecognitionStarted || mVirtualCallStarted) {
return true;
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d25d9ed..8a2eab3 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -25,11 +25,15 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.util.Log;
+import android.os.SystemProperties;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
@@ -41,9 +45,13 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.Iterator;
+import android.telecom.TelecomManager;
/**
* A Bluetooth Handset StateMachine
@@ -68,7 +76,7 @@
@VisibleForTesting
public class HeadsetStateMachine extends StateMachine {
private static final String TAG = "HeadsetStateMachine";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String HEADSET_NAME = "bt_headset_name";
private static final String HEADSET_NREC = "bt_headset_nrec";
@@ -95,20 +103,66 @@
static final int DIALING_OUT_RESULT = 14;
static final int VOICE_RECOGNITION_RESULT = 15;
+ static final int QUERY_PHONE_STATE_AT_SLC = 18;
+ static final int SEND_INCOMING_CALL_IND = 19;
+ static final int VOIP_CALL_STATE_CHANGED_ALERTING = 20;
+ static final int VOIP_CALL_STATE_CHANGED_ACTIVE = 21;
+ static final int CS_CALL_STATE_CHANGED_ALERTING = 22;
+ static final int CS_CALL_STATE_CHANGED_ACTIVE = 23;
+ static final int A2DP_STATE_CHANGED = 24;
+ static final int UPDATE_CALL_TYPE = 25;
+ static final int RESUME_A2DP = 26;
+
static final int STACK_EVENT = 101;
private static final int CLCC_RSP_TIMEOUT = 104;
+ private static final int PROCESS_CPBR = 105;
private static final int CONNECT_TIMEOUT = 201;
private static final int CLCC_RSP_TIMEOUT_MS = 5000;
+ private static final int QUERY_PHONE_STATE_CHANGED_DELAYED = 100;
// NOTE: the value is not "final" - it is modified in the unit tests
@VisibleForTesting static int sConnectTimeoutMs = 30000;
private static final HeadsetAgIndicatorEnableState DEFAULT_AG_INDICATOR_ENABLE_STATE =
new HeadsetAgIndicatorEnableState(true, true, true, true);
+ // delay call indicators and some remote devices are not able to handle
+ // indicators back to back, especially in VOIP scenarios.
+ /* Delay between call dialling, alerting updates for VOIP call */
+ private static final int VOIP_CALL_ALERTING_DELAY_TIME_MSEC = 800;
+ /* Delay between call alerting, active updates for VOIP call */
+ private static final int VOIP_CALL_ACTIVE_DELAY_TIME_MSEC =
+ VOIP_CALL_ALERTING_DELAY_TIME_MSEC + 50;
+ private int CS_CALL_ALERTING_DELAY_TIME_MSEC = 800;
+ private int CS_CALL_ACTIVE_DELAY_TIME_MSEC = 10;
+ private static final int INCOMING_CALL_IND_DELAY = 200;
+ private static final int MAX_RETRY_CONNECT_COUNT = 2;
+ // Blacklist remote device addresses to send incoimg call indicators with delay of 200ms
+ private static final String [] BlacklistDeviceAddrToDelayCallInd =
+ {"00:15:83", /* Beiqi Carkit */
+ "2a:eb:00", /* BIAC Carkit */
+ "30:53:00", /* BIAC series */
+ "00:17:53", /* ADAYO Carkit */
+ "40:ef:4c", /* Road Rover Carkit */
+ "00:07:04", /* Tiguan RNS315 */
+ };
+ private static final String [] BlacklistDeviceForSendingVOIPCallIndsBackToBack =
+ {"f4:15:fd"}; /* Rongwei 360 Car */
+ private static final String VOIP_CALL_NUMBER = "10000000";
+
+ //VR app launched successfully
+ private static final int VR_SUCCESS = 1;
+
+ //VR app failed to launch
+ private static final int VR_FAILURE = 0;
+
private final BluetoothDevice mDevice;
+ // maintain call states in state machine as well
+ private final HeadsetCallState mStateMachineCallState =
+ new HeadsetCallState(0, 0, 0, "", 0);
+
// State machine states
private final Disconnected mDisconnected = new Disconnected();
private final Connecting mConnecting = new Connecting();
@@ -118,17 +172,33 @@
private final AudioConnecting mAudioConnecting = new AudioConnecting();
private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting();
private HeadsetStateBase mPrevState;
+ private HeadsetStateBase mCurrentState;
+
+ // used for synchronizing mCurrentState set/get
+ private final Object mLock = new Object();
// Run time dependencies
private final HeadsetService mHeadsetService;
private final AdapterService mAdapterService;
private final HeadsetNativeInterface mNativeInterface;
private final HeadsetSystemInterface mSystemInterface;
+ private ConnectivityManager mConnectivityManager;
// Runtime states
private int mSpeakerVolume;
private int mMicVolume;
private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
+ private boolean mA2dpSuspend;
+ private boolean mIsCsCall = true;
+ private boolean mPendingScoForVR = false;
+ private boolean mIsCallIndDelay = false;
+ private boolean mIsBlacklistedDevice = false;
+ private int retryConnectCount = 0;
+ //ConcurrentLinkeQueue is used so that it is threadsafe
+ private ConcurrentLinkedQueue<HeadsetCallState> mPendingCallStates =
+ new ConcurrentLinkedQueue<HeadsetCallState>();
+ private ConcurrentLinkedQueue<HeadsetCallState> mDelayedCSCallStates =
+ new ConcurrentLinkedQueue<HeadsetCallState>();
// The timestamp when the device entered connecting/connected state
private long mConnectingTimestampMs = Long.MIN_VALUE;
// Audio Parameters like NREC
@@ -138,9 +208,19 @@
// HSP specific
private boolean mNeedDialingOutReply;
+ // Hash for storing the A2DP connection states
+ private HashMap<BluetoothDevice, Integer> mA2dpConnState =
+ new HashMap<BluetoothDevice, Integer>();
+ // Hash for storing the A2DP play states
+ private HashMap<BluetoothDevice, Integer> mA2dpPlayState =
+ new HashMap<BluetoothDevice, Integer>();
+
// Keys are AT commands, and values are the company IDs.
private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
+ /* Retry outgoing connection after this time if the first attempt fails */
+ private static final int RETRY_CONNECT_TIME_SEC = 2500;
+
static {
VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
@@ -172,6 +252,8 @@
mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
// Create phonebook helper
mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
+ mConnectivityManager = (ConnectivityManager)
+ mHeadsetService.getSystemService(mHeadsetService.CONNECTIVITY_SERVICE);
// Initialize state machine
addState(mDisconnected);
addState(mConnecting);
@@ -181,6 +263,15 @@
addState(mAudioConnecting);
addState(mAudioDisconnecting);
setInitialState(mDisconnected);
+
+ if (isDeviceBlacklistedForSendingCallIndsBackToBack()) {
+ CS_CALL_ALERTING_DELAY_TIME_MSEC = 0;
+ CS_CALL_ACTIVE_DELAY_TIME_MSEC = 0;
+ Log.w(TAG, "alerting delay " + CS_CALL_ALERTING_DELAY_TIME_MSEC +
+ " active delay " + CS_CALL_ACTIVE_DELAY_TIME_MSEC);
+ }
+
+ Log.i(TAG," Exiting HeadsetStateMachine constructor for device :" + device);
}
static HeadsetStateMachine make(BluetoothDevice device, Looper looper,
@@ -189,6 +280,7 @@
HeadsetStateMachine stateMachine =
new HeadsetStateMachine(device, looper, headsetService, adapterService,
nativeInterface, systemInterface);
+ Log.i(TAG," Starting StateMachine device: " + device);
stateMachine.start();
Log.i(TAG, "Created state machine " + stateMachine + " for " + device);
return stateMachine;
@@ -200,20 +292,39 @@
Log.w(TAG, "destroy(), stateMachine is null");
return;
}
- stateMachine.quitNow();
stateMachine.cleanup();
+ stateMachine.quitNow();
}
public void cleanup() {
+ Log.i(TAG," destroy, current state " + getCurrentHeadsetStateMachineState());
+ if (getCurrentHeadsetStateMachineState() == mAudioOn) {
+ mAudioOn.broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ mAudioOn.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+ if(getCurrentHeadsetStateMachineState() == mConnected){
+ mConnected.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+ if(getCurrentHeadsetStateMachineState() == mConnecting){
+ mConnecting.broadcastConnectionState(mDevice, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
if (mPhonebook != null) {
mPhonebook.cleanup();
}
+ if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED &&
+ !mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+ sendVoipConnectivityNetworktype(false);
+ }
mAudioParams.clear();
}
public void dump(StringBuilder sb) {
ProfileService.println(sb, " mCurrentDevice: " + mDevice);
- ProfileService.println(sb, " mCurrentState: " + getCurrentState());
+ ProfileService.println(sb, " mCurrentState: " + mCurrentState);
ProfileService.println(sb, " mPrevState: " + mPrevState);
ProfileService.println(sb, " mConnectionState: " + getConnectionState());
ProfileService.println(sb, " mAudioState: " + getAudioState());
@@ -249,6 +360,11 @@
throw new IllegalStateException("mPrevState is null on enter()");
}
enforceValidConnectionStateTransition();
+
+ synchronized(mLock) {
+ mCurrentState = this;
+ Log.e(TAG, "Setting mCurrentState as " + mCurrentState);
+ }
}
@Override
@@ -411,6 +527,13 @@
}
+ public HeadsetStateBase getCurrentHeadsetStateMachineState() {
+ synchronized(mLock) {
+ Log.e(TAG, "returning mCurrentState as " + mCurrentState);
+ return mCurrentState;
+ }
+ }
+
class Disconnected extends HeadsetStateBase {
@Override
int getConnectionStateInt() {
@@ -430,12 +553,21 @@
updateAgIndicatorEnableState(null);
mNeedDialingOutReply = false;
mAudioParams.clear();
+
+ // reset call information
+ mStateMachineCallState.mNumActive = 0;
+ mStateMachineCallState.mNumHeld = 0;
+ mStateMachineCallState.mCallState = 0;
+ mStateMachineCallState.mNumber = "";
+ mStateMachineCallState.mType = 0;
+
broadcastStateTransitions();
// Remove the state machine for unbonded devices
if (mPrevState != null
&& mAdapterService.getBondState(mDevice) == BluetoothDevice.BOND_NONE) {
getHandler().post(() -> mHeadsetService.removeStateMachine(mDevice));
}
+ mIsBlacklistedDevice = false;
}
@Override
@@ -449,6 +581,13 @@
"CONNECT failed, device=" + device + ", currentDevice=" + mDevice);
break;
}
+
+ stateLogD(" retryConnectCount = " + retryConnectCount);
+ if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT) {
+ // max attempts reached, reset it to 0
+ retryConnectCount = 0;
+ break;
+ }
if (!mNativeInterface.connectHfp(device)) {
stateLogE("CONNECT failed for connectHfp(" + device + ")");
// No state transition is involved, fire broadcast immediately
@@ -456,6 +595,7 @@
BluetoothProfile.STATE_DISCONNECTED);
break;
}
+ retryConnectCount++;
transitionTo(mConnecting);
break;
case DISCONNECT:
@@ -467,6 +607,10 @@
case DEVICE_STATE_CHANGED:
stateLogD("Ignoring DEVICE_STATE_CHANGED event");
break;
+ case UPDATE_CALL_TYPE:
+ stateLogD("UPDATE_CALL_TYPE event");
+ processIntentUpdateCallType((Intent) message.obj);
+ break;
case STACK_EVENT:
HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
stateLogD("STACK_EVENT: " + event);
@@ -545,6 +689,19 @@
super.enter();
mConnectingTimestampMs = SystemClock.uptimeMillis();
sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+ mSystemInterface.queryPhoneState();
+ // update call states in StateMachine
+ mStateMachineCallState.mNumActive =
+ mSystemInterface.getHeadsetPhoneState().getNumActiveCall();
+ mStateMachineCallState.mNumHeld =
+ mSystemInterface.getHeadsetPhoneState().getNumHeldCall();
+ mStateMachineCallState.mCallState =
+ mSystemInterface.getHeadsetPhoneState().getCallState();
+ mStateMachineCallState.mNumber =
+ mSystemInterface.getHeadsetPhoneState().getNumber();
+ mStateMachineCallState.mType =
+ mSystemInterface.getHeadsetPhoneState().getType();
+
broadcastStateTransitions();
}
@@ -567,12 +724,27 @@
transitionTo(mDisconnected);
break;
}
- case CALL_STATE_CHANGED:
- stateLogD("ignoring CALL_STATE_CHANGED event");
- break;
case DEVICE_STATE_CHANGED:
stateLogD("ignoring DEVICE_STATE_CHANGED event");
break;
+ case A2DP_STATE_CHANGED:
+ stateLogD("A2DP_STATE_CHANGED event");
+ processIntentA2dpPlayStateChanged(message.arg1);
+ break;
+ case CALL_STATE_CHANGED: {
+ HeadsetCallState callState = (HeadsetCallState) message.obj;
+ processCallState(callState, false);
+ break;
+ }
+ case UPDATE_CALL_TYPE:
+ stateLogD("UPDATE_CALL_TYPE event");
+ processIntentUpdateCallType((Intent) message.obj);
+ break;
+ case RESUME_A2DP: {
+ stateLogD("RESUME_A2DP evt, resuming A2DP");
+ mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+ break;
+ }
case STACK_EVENT:
HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
stateLogD("STACK_EVENT: " + event);
@@ -617,8 +789,7 @@
processAtCops(event.device);
break;
case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
- Log.w(TAG, "Connecting: Unexpected CLCC event for" + event.device);
- processAtClcc(event.device);
+ stateLogW("Connecting: Unexpected CLCC event for" + event.device);
break;
case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
stateLogW("Unexpected unknown AT event for" + event.device + ", cmd="
@@ -664,6 +835,15 @@
switch (state) {
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
stateLogW("Disconnected");
+ processWBSEvent(HeadsetHalConstants.BTHF_WBS_NO);
+ stateLogD(" retryConnectCount = " + retryConnectCount);
+ if(retryConnectCount == 1) {
+ Log.d(TAG," retry once more ");
+ sendMessageDelayed(CONNECT, mDevice, RETRY_CONNECT_TIME_SEC);
+ } else if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT) {
+ // we already tried twice.
+ retryConnectCount = 0;
+ }
transitionTo(mDisconnected);
break;
case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
@@ -671,6 +851,7 @@
break;
case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
stateLogD("SLC connected");
+ retryConnectCount = 0;
transitionTo(mConnected);
break;
case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
@@ -814,6 +995,15 @@
stateLogW("Failed to start voice recognition");
break;
}
+
+ if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+ HeadsetA2dpSync.A2DP_SUSPENDED_BY_VR, mDevice) == true) {
+ Log.d(TAG, "mesg VOICE_RECOGNITION_START: A2DP is playing,"+
+ " return and establish SCO after A2DP supended");
+ break;
+ }
+ // create SCO since there is no A2DP playback
+ mNativeInterface.connectAudio(mDevice);
break;
}
case VOICE_RECOGNITION_STOP: {
@@ -830,13 +1020,63 @@
break;
}
case CALL_STATE_CHANGED: {
+ boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
HeadsetCallState callState = (HeadsetCallState) message.obj;
- if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
- stateLogW("processCallState: failed to update call state " + callState);
- break;
+ // for PTS, send the indicators as is
+ if (isPts) {
+ if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
+ stateLogW("processCallState: failed to update call state " + callState);
+ break;
+ }
+ }
+ else
+ processCallStatesDelayed(callState, false);
+ break;
+ }
+ case CS_CALL_STATE_CHANGED_ALERTING: {
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+ // top of the queue is call alerting
+ if(tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+ {
+ stateLogD("alerting message timer expired, send alerting update");
+ //dequeue the alerting call state;
+ mDelayedCSCallStates.poll();
+ processCallState(tempCallState, false);
+ }
+
+ // top of the queue == call active
+ tempCallState = mDelayedCSCallStates.peek();
+ if (tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+ {
+ stateLogD("alerting message timer expired, send delayed active mesg");
+ //send delayed message for call active;
+ Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ACTIVE);
+ msg.arg1 = 0;
+ sendMessageDelayed(msg, CS_CALL_ACTIVE_DELAY_TIME_MSEC);
}
break;
}
+ case CS_CALL_STATE_CHANGED_ACTIVE: {
+ // get the top of the Q
+ // top of the queue == call active
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+ if (tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+ {
+ stateLogD("active message timer expired, send active update");
+ //dequeue the active call state;
+ mDelayedCSCallStates.poll();
+ processCallState(tempCallState, false);
+ }
+ }
+ break;
+ case A2DP_STATE_CHANGED:
+ stateLogD("A2DP_STATE_CHANGED event");
+ processIntentA2dpPlayStateChanged(message.arg1);
+ break;
case DEVICE_STATE_CHANGED:
mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
break;
@@ -867,8 +1107,22 @@
break;
}
mNativeInterface.atResponseCode(mDevice,
- message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
+ message.arg1 == VR_SUCCESS ? HeadsetHalConstants.AT_RESPONSE_OK
: HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ if (message.arg1 != VR_SUCCESS) {
+ Log.d(TAG, "VOICE_RECOGNITION_RESULT: not creating SCO since VR app"+
+ " failed to start VR");
+ break;
+ }
+
+ if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+ HeadsetA2dpSync.A2DP_SUSPENDED_BY_VR, mDevice) == true) {
+ Log.d(TAG, "mesg VOICE_RECOGNITION_START: A2DP is playing,"+
+ " return and establish SCO after A2DP supended");
+ break;
+ }
+ // create SCO since there is no A2DP playback
+ mNativeInterface.connectAudio(mDevice);
break;
}
case DIALING_OUT_RESULT: {
@@ -888,6 +1142,25 @@
case INTENT_CONNECTION_ACCESS_REPLY:
handleAccessPermissionResult((Intent) message.obj);
break;
+ case PROCESS_CPBR:
+ Intent intent = (Intent) message.obj;
+ processCpbr(intent);
+ break;
+ case SEND_INCOMING_CALL_IND:
+ HeadsetCallState callState =
+ new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
+ mSystemInterface.getHeadsetPhoneState().getNumber(),
+ mSystemInterface.getHeadsetPhoneState().getType());
+ mNativeInterface.phoneStateChange(mDevice, callState);
+ break;
+ case QUERY_PHONE_STATE_AT_SLC:
+ stateLogD("Update call states after SLC is up");
+ mSystemInterface.queryPhoneState();
+ break;
+ case UPDATE_CALL_TYPE:
+ stateLogD("UPDATE_CALL_TYPE event");
+ processIntentUpdateCallType((Intent) message.obj);
+ break;
case STACK_EVENT:
HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
stateLogD("STACK_EVENT: " + event);
@@ -979,6 +1252,7 @@
break;
case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
stateLogE("processConnectionEvent: SLC connected again, shouldn't happen");
+ retryConnectCount = 0;
break;
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
stateLogI("processConnectionEvent: Disconnecting");
@@ -986,6 +1260,7 @@
break;
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
stateLogI("processConnectionEvent: Disconnected");
+ processWBSEvent(HeadsetHalConstants.BTHF_WBS_NO);
transitionTo(mDisconnected);
break;
default:
@@ -1020,12 +1295,28 @@
// Reset NREC on connect event. Headset will override later
processNoiseReductionEvent(true);
// Query phone state for initial setup
- mSystemInterface.queryPhoneState();
+ sendMessageDelayed(QUERY_PHONE_STATE_AT_SLC, QUERY_PHONE_STATE_CHANGED_DELAYED);
+ // Checking for the Blacklisted device Addresses
+ mIsBlacklistedDevice = isConnectedDeviceBlacklistedforIncomingCall();
+ if (mSystemInterface.isInCall() || mSystemInterface.isRinging()) {
+ stateLogW("Connected: enter: suspending A2DP for Call since SLC connected");
+ // suspend A2DP since call is there
+ mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+ HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, mDevice);
+ }
// Remove pending connection attempts that were deferred during the pending
// state. This is to prevent auto connect attempts from disconnecting
// devices that previously successfully connected.
removeDeferredMessages(CONNECT);
}
+ if ((mPrevState == mAudioOn) || (mPrevState == mAudioDisconnecting)||
+ (mPrevState == mAudioConnecting)) {
+ if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+ // SCO disconnected, resume A2DP if there is no call
+ stateLogD("SCO disconnected, set A2DPsuspended to false");
+ mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+ }
+ }
broadcastStateTransitions();
}
@@ -1056,9 +1347,13 @@
break;
case CONNECT_AUDIO:
stateLogD("CONNECT_AUDIO, device=" + mDevice);
- mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+ int a2dpState = mHeadsetService.getHfpA2DPSyncInterface().isA2dpPlaying();
+ if (!mHeadsetService.isScoAcceptable(mDevice)|| (a2dpState == HeadsetA2dpSync.A2DP_PLAYING)) {
+ stateLogW("No Active/Held call, no call setup,and no in-band ringing,"
+ + " or A2Dp is playing, not allowing SCO, device=" + mDevice);
+ break;
+ }
if (!mNativeInterface.connectAudio(mDevice)) {
- mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
stateLogE("Failed to connect SCO audio for " + mDevice);
// No state change involved, fire broadcast immediately
broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
@@ -1071,6 +1366,11 @@
stateLogD("ignore DISCONNECT_AUDIO, device=" + mDevice);
// ignore
break;
+ case RESUME_A2DP: {
+ stateLogD("RESUME_A2DP evt, resuming A2DP");
+ mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+ break;
+ }
default:
return super.processMessage(message);
}
@@ -1092,6 +1392,12 @@
BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
break;
}
+ if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+ stateLogI("Sco connected for call other than CS, check network type");
+ sendVoipConnectivityNetworktype(true);
+ } else {
+ stateLogI("Sco connected for CS call, do not check network type");
+ }
stateLogI("processAudioEvent: audio connected");
transitionTo(mAudioOn);
break;
@@ -1110,6 +1416,12 @@
transitionTo(mAudioConnecting);
break;
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+ if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
+ // SCO disconnected, resume A2DP if there is no call
+ stateLogD("SCO disconnected, set A2DPsuspended to false");
+ mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+ }
+ break;
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
// ignore
break;
@@ -1205,7 +1517,14 @@
&& !hasDeferredMessages(DISCONNECT_AUDIO)) {
mHeadsetService.setActiveDevice(mDevice);
}
- setAudioParameters();
+ // If current device is TWSPLUS device and peer TWSPLUS device is already
+ // has SCO, dont need to update teh Audio Manager
+ if (mAdapterService.isTwsPlusDevice(mDevice) &&
+ mHeadsetService.isAudioConnected(mHeadsetService.getTwsPlusConnectedPeer(mDevice))) {
+ stateLogW("Dont update Audio as this TWS peer eSCO");
+ } else {
+ setAudioParameters();
+ }
broadcastStateTransitions();
}
@@ -1224,14 +1543,24 @@
stateLogW("DISCONNECT, device " + device + " not connected");
break;
}
- // Disconnect BT SCO first
- if (!mNativeInterface.disconnectAudio(mDevice)) {
- stateLogW("DISCONNECT failed, device=" + mDevice);
- // if disconnect BT SCO failed, transition to mConnected state to force
- // disconnect device
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ //for twsplus device, don't disconnect the SCO for app
+ //force the disocnnect of HF and in turn let SCO shutdown
+ //so that SCO SM handles it gracefully
+ if (!mNativeInterface.disconnectHfp(device)) {
+ stateLogW("DISCONNECT failed TWS case, device=" + mDevice);
+ }
+ transitionTo(mDisconnecting);
+ } else {
+ // Disconnect BT SCO first
+ if (!mNativeInterface.disconnectAudio(mDevice)) {
+ stateLogW("DISCONNECT failed, device=" + mDevice);
+ // if disconnect BT SCO failed, transition to mConnected state to force
+ // disconnect device
+ }
+ deferMessage(obtainMessage(DISCONNECT, mDevice));
+ transitionTo(mAudioDisconnecting);
}
- deferMessage(obtainMessage(DISCONNECT, mDevice));
- transitionTo(mAudioDisconnecting);
break;
}
case CONNECT_AUDIO: {
@@ -1291,6 +1620,22 @@
switch (state) {
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
stateLogI("processAudioEvent: audio disconnected by remote");
+ if (mAdapterService.isTwsPlusDevice(mDevice) && mHeadsetService.isAudioOn()) {
+ //If It is TWSP device, make sure SCO is not active on
+ //any devices before letting Audio knowing about it
+ stateLogI("TWS+ device and other SCO is still Active, no BT_SCO=off");
+ } else {
+ if(mSystemInterface.getAudioManager().isSpeakerphoneOn()) {
+ mSystemInterface.getAudioManager().setSpeakerphoneOn(true);
+ }
+ }
+ if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+ stateLogI("Sco disconnected for call other than CS, check network type");
+ sendVoipConnectivityNetworktype(false);
+ mSystemInterface.getHeadsetPhoneState().setIsCsCall(true);
+ } else {
+ stateLogI("Sco disconnected for CS call, do not check network type");
+ }
transitionTo(mConnected);
break;
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
@@ -1305,10 +1650,11 @@
private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+ stateLogD(" mSpeakerVolume = " + mSpeakerVolume + " volValue = " + volumeValue);
if (mSpeakerVolume != volumeValue) {
mSpeakerVolume = volumeValue;
mNativeInterface.setVolume(device, HeadsetHalConstants.VOLUME_TYPE_SPK,
- mSpeakerVolume);
+ mSpeakerVolume);
}
}
}
@@ -1357,6 +1703,15 @@
switch (state) {
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
stateLogI("processAudioEvent: audio disconnected");
+ if (mAdapterService.isTwsPlusDevice(mDevice) && mHeadsetService.isAudioOn()) {
+ //If It is TWSP device, make sure SCO is not active on
+ //any devices before letting Audio knowing about it
+ stateLogI("TWS+ device and other SCO is still Active, no BT_SCO=off");
+ } else {
+ if(mSystemInterface.getAudioManager().isSpeakerphoneOn()) {
+ mSystemInterface.getAudioManager().setSpeakerphoneOn(true);
+ }
+ }
transitionTo(mConnected);
break;
case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
@@ -1401,7 +1756,8 @@
*/
@VisibleForTesting
public synchronized int getConnectionState() {
- HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+ //getCurrentState()
+ HeadsetStateBase state = (HeadsetStateBase) getCurrentHeadsetStateMachineState();
if (state == null) {
return BluetoothHeadset.STATE_DISCONNECTED;
}
@@ -1416,7 +1772,8 @@
* {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
*/
public synchronized int getAudioState() {
- HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+ //getCurrentState()
+ HeadsetStateBase state = (HeadsetStateBase) getCurrentHeadsetStateMachineState();
if (state == null) {
return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
}
@@ -1427,6 +1784,14 @@
return mConnectingTimestampMs;
}
+ public boolean isPendingRetryConnect() {
+ if((getConnectionState() == BluetoothHeadset.STATE_DISCONNECTED) &&
+ (retryConnectCount > 0) && hasMessages(CONNECT)) {
+ return true;
+ }
+ return false;
+ }
+
/*
* Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
*/
@@ -1505,8 +1870,9 @@
}
if ((number == null) || (number.length() == 0)) {
dialNumber = mPhonebook.getLastDialledNumber();
- if (dialNumber == null) {
- Log.w(TAG, "processDialCall, last dial number null");
+ log("dialNumber: " + dialNumber);
+ if ((dialNumber == null) || (dialNumber.length() == 0)) {
+ Log.w(TAG, "processDialCall, last dial number null or empty ");
mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
return;
}
@@ -1558,13 +1924,16 @@
private void processVolumeEvent(int volumeType, int volume) {
// Only current active device can change SCO volume
- if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (!adapterService.isTwsPlusDevice(mDevice) &&
+ !mDevice.equals(mHeadsetService.getActiveDevice())) {
Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
return;
}
if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
mSpeakerVolume = volume;
- int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
+ int flag = (getCurrentHeadsetStateMachineState() == mAudioOn)
+ ? AudioManager.FLAG_SHOW_UI : 0;
mSystemInterface.getAudioManager()
.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
} else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
@@ -1575,6 +1944,279 @@
}
}
+ private void processCallStatesDelayed(HeadsetCallState callState, boolean isVirtualCall)
+ {
+ log("Enter processCallStatesDelayed");
+ final HeadsetPhoneState mPhoneState = mSystemInterface.getHeadsetPhoneState();
+ if (callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING)
+ {
+ // at this point, queue should be empty.
+ processCallState(callState, false);
+ }
+ // update is for call alerting
+ else if (callState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING &&
+ mStateMachineCallState.mNumActive == callState.mNumActive &&
+ mStateMachineCallState.mNumHeld == callState.mNumHeld &&
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING)
+ {
+ log("Queue alerting update, send alerting delayed mesg");
+ //Q the call state;
+ mDelayedCSCallStates.add(callState);
+
+ //send delayed message for call alerting;
+ Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ALERTING);
+ msg.arg1 = 0;
+ sendMessageDelayed(msg, CS_CALL_ALERTING_DELAY_TIME_MSEC);
+ }
+ // call moved to active from alerting state
+ else if (mStateMachineCallState.mNumActive == 0 &&
+ callState.mNumActive == 1 &&
+ mStateMachineCallState.mNumHeld == callState.mNumHeld &&
+ (mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ))
+ {
+ log("Call moved to active state from alerting");
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+ //if (top of the Q == alerting)
+ if( tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+ {
+ log("Call is active, Queue it, top of Queue is alerting");
+ //Q active update;
+ mDelayedCSCallStates.add(callState);
+ }
+ else
+ // Q is empty
+ {
+ log("is Q empty " + mDelayedCSCallStates.isEmpty());
+ log("Call is active, Queue it, send delayed active mesg");
+ //Q active update;
+ mDelayedCSCallStates.add(callState);
+ //send delayed message for call active;
+ Message msg = obtainMessage(CS_CALL_STATE_CHANGED_ACTIVE);
+ msg.arg1 = 0;
+ sendMessageDelayed(msg, CS_CALL_ACTIVE_DELAY_TIME_MSEC);
+ }
+ }
+ // call setup or call ended
+ else if((mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ) &&
+ callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE &&
+ mStateMachineCallState.mNumActive == callState.mNumActive &&
+ mStateMachineCallState.mNumHeld == callState.mNumHeld)
+ {
+ log("call setup or call is ended");
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+ //if (top of the Q == alerting)
+ if(tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+ {
+ log("Call is ended, remove delayed alerting mesg");
+ removeMessages(CS_CALL_STATE_CHANGED_ALERTING);
+ //DeQ(alerting);
+ mDelayedCSCallStates.poll();
+ // send 2,3 although the call is ended to make sure that we are sending 2,3 always
+ processCallState(tempCallState, false);
+
+ // update the top of the Q entry so that we process the active
+ // call entry from the Q below
+ tempCallState = mDelayedCSCallStates.peek();
+ }
+
+ //if (top of the Q == active)
+ if (tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+ {
+ log("Call is ended, remove delayed active mesg");
+ removeMessages(CS_CALL_STATE_CHANGED_ACTIVE);
+ //DeQ(active);
+ mDelayedCSCallStates.poll();
+ }
+ // send current call state which will take care of sending call end indicator
+ processCallState(callState, false);
+ } else {
+ HeadsetCallState tempCallState;
+
+ // if there are pending call states to be sent, send them now
+ if (mDelayedCSCallStates.isEmpty() != true)
+ {
+ log("new call update, removing pending alerting, active messages");
+ // remove pending delayed call states
+ removeMessages(CS_CALL_STATE_CHANGED_ALERTING);
+ removeMessages(CS_CALL_STATE_CHANGED_ACTIVE);
+ }
+
+ while (mDelayedCSCallStates.isEmpty() != true)
+ {
+ tempCallState = mDelayedCSCallStates.poll();
+ if (tempCallState != null)
+ {
+ processCallState(tempCallState, false);
+ }
+ }
+ // it is incoming call or MO call in non-alerting, non-active state.
+ processCallState(callState, isVirtualCall);
+ }
+ log("Exit processCallStatesDelayed");
+ }
+
+ private void processCallState(HeadsetCallState callState, boolean isVirtualCall) {
+ /* If active call is ended, no held call is present, disconnect SCO
+ * and fake the MT Call indicators. */
+ boolean isPts =
+ SystemProperties.getBoolean("vendor.bt.pts.certification", false);
+ if (!isPts) {
+ log("mIsBlacklistedDevice:" + mIsBlacklistedDevice);
+ if (mIsBlacklistedDevice &&
+ mStateMachineCallState.mNumActive == 1 &&
+ callState.mNumActive == 0 &&
+ callState.mNumHeld == 0 &&
+ callState.mCallState == HeadsetHalConstants.CALL_STATE_INCOMING) {
+
+ log("Disconnect SCO since active call is ended," +
+ "only waiting call is there");
+ Message m = obtainMessage(DISCONNECT_AUDIO);
+ m.obj = mDevice;
+ sendMessage(m);
+
+ log("Send Idle call indicators once Active call disconnected.");
+ // TODO: cross check this
+ mStateMachineCallState.mCallState =
+ HeadsetHalConstants.CALL_STATE_IDLE;
+ HeadsetCallState updateCallState = new HeadsetCallState(callState.mNumActive,
+ callState.mNumHeld,
+ HeadsetHalConstants.CALL_STATE_IDLE,
+ callState.mNumber,
+ callState.mType);
+ mNativeInterface.phoneStateChange(mDevice, updateCallState);
+ mIsCallIndDelay = true;
+ }
+ }
+ mStateMachineCallState.mNumActive = callState.mNumActive;
+ mStateMachineCallState.mNumHeld = callState.mNumHeld;
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+ if ( !isVirtualCall && tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING &&
+ callState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING) {
+ log("update call state as dialing since alerting update is in Q");
+ log("current call state is " + mStateMachineCallState.mCallState);
+ callState.mCallState = HeadsetHalConstants.CALL_STATE_DIALING;
+ }
+
+ mStateMachineCallState.mCallState = callState.mCallState;
+ mStateMachineCallState.mNumber = callState.mNumber;
+ mStateMachineCallState.mType = callState.mType;
+
+ log("processCallState: mNumActive: " + callState.mNumActive + " mNumHeld: "
+ + callState.mNumHeld + " mCallState: " + callState.mCallState);
+ log("processCallState: mNumber: " + callState.mNumber + " mType: " + callState.mType);
+
+ processA2dpState(callState);
+ }
+
+ /* This function makes sure that we send a2dp suspend before updating on Incomming call status.
+ There may problem with some headsets if send ring and a2dp is not suspended,
+ so here we suspend stream if active before updating remote.We resume streaming once
+ callstate is idle and there are no active or held calls. */
+
+ private void processA2dpState(HeadsetCallState callState) {
+ int a2dpState = mHeadsetService.getHfpA2DPSyncInterface().isA2dpPlaying();
+ Log.d(TAG, "processA2dpState: isA2dpPlaying() " + a2dpState);
+
+ if ((mSystemInterface.isInCall() || mSystemInterface.isRinging()) &&
+ getConnectionState() == BluetoothHeadset.STATE_CONNECTED) {
+ // if A2DP is playing, add CS call states and return
+ if (mHeadsetService.getHfpA2DPSyncInterface().suspendA2DP(
+ HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, mDevice) == true) {
+ Log.d(TAG, "processA2dpState: A2DP is playing, suspending it,"+
+ "cache the call state for future");
+ mPendingCallStates.add(callState);
+ return;
+ }
+ }
+
+ if (getCurrentHeadsetStateMachineState() != mDisconnected) {
+ log("No A2dp playing to suspend, mIsCallIndDelay: " + mIsCallIndDelay +
+ " mPendingCallStates.size(): " + mPendingCallStates.size());
+ //When MO call creation and disconnection done back to back, Make sure to send
+ //the call indicators in a sequential way to remote
+ if (mPendingCallStates.size() != 0) {
+ Log.d(TAG, "Cache the call state, PendingCallStates list is not empty");
+ mPendingCallStates.add(callState);
+ return;
+ }
+ if (mIsCallIndDelay) {
+ mIsCallIndDelay = false;
+ sendMessageDelayed(SEND_INCOMING_CALL_IND, INCOMING_CALL_IND_DELAY);
+ } else {
+ mNativeInterface.phoneStateChange(mDevice, callState);
+ }
+ }
+ }
+
+ private void processIntentUpdateCallType(Intent intent) {
+ mIsCsCall = intent.getBooleanExtra(TelecomManager.EXTRA_CALL_TYPE_CS, true);
+ Log.d(TAG, "processIntentUpdateCallType " + mIsCsCall);
+ final HeadsetPhoneState mPhoneState = mSystemInterface.getHeadsetPhoneState();
+ mPhoneState.setIsCsCall(mIsCsCall);
+ if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ if (!mPhoneState.getIsCsCall()) {
+ log("processIntentUpdateCallType, Non CS call, check for network type");
+ sendVoipConnectivityNetworktype(true);
+ } else {
+ log("processIntentUpdateCallType, CS call, do not check for network type");
+ }
+ } else {
+ log("processIntentUpdateCallType: Sco not yet connected");
+ }
+ }
+
+ private void processIntentA2dpPlayStateChanged(int a2dpState) {
+ Log.d(TAG, "Enter processIntentA2dpPlayStateChanged(): a2dp state "+
+ a2dpState);
+ if (mHeadsetService.isVRStarted()) {
+ Log.d(TAG, "VR is in started state");
+ if (mDevice.equals(mHeadsetService.getActiveDevice())) {
+ Log.d(TAG, "creating SCO for " + mDevice);
+ mNativeInterface.connectAudio(mDevice);
+ }
+ } else if (mSystemInterface.isInCall() || mHeadsetService.isVirtualCallStarted()){
+ //send incoming phone status to remote device
+ Log.d(TAG, "A2dp is suspended, updating phone states");
+ Iterator<HeadsetCallState> it = mPendingCallStates.iterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ HeadsetCallState callState = it.next();
+ Log.d(TAG, "mIsCallIndDelay: " + mIsCallIndDelay);
+ mNativeInterface.phoneStateChange(mDevice, callState);
+ it.remove();
+ }
+ } else {
+ Log.d(TAG, "There are no pending call state changes");
+ }
+ } else {
+ Log.d(TAG, "A2DP suspended when there is no CS/VOIP calls or VR, resuming A2DP");
+ //When A2DP is suspended and the call is terminated,
+ //clean up the PendingCallStates list
+ Iterator<HeadsetCallState> it = mPendingCallStates.iterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ HeadsetCallState callState = it.next();
+ mNativeInterface.phoneStateChange(mDevice, callState);
+ it.remove();
+ }
+ }
+ mHeadsetService.getHfpA2DPSyncInterface().releaseA2DP(mDevice);
+ }
+ Log.d(TAG, "Exit processIntentA2dpPlayStateChanged()");
+ }
+
private void processNoiseReductionEvent(boolean enable) {
String prevNrec = mAudioParams.getOrDefault(HEADSET_NREC, HEADSET_AUDIO_FEATURE_OFF);
String newNrec = enable ? HEADSET_AUDIO_FEATURE_ON : HEADSET_AUDIO_FEATURE_OFF;
@@ -1625,30 +2267,57 @@
}
private void processAtCind(BluetoothDevice device) {
- int call, callSetup;
+ int call, callSetup, call_state, service, signal;
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
/* Handsfree carkits expect that +CIND is properly responded to
Hence we ensure that a proper response is sent
for the virtual call too.*/
if (mHeadsetService.isVirtualCallStarted()) {
- call = 1;
+ call = mStateMachineCallState.mNumActive;
callSetup = 0;
} else {
// regular phone call
- call = phoneState.getNumActiveCall();
- callSetup = phoneState.getNumHeldCall();
+ call = mStateMachineCallState.mNumActive;
+ callSetup = mStateMachineCallState.mNumHeld;
+ }
+ if(tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING)
+ call_state = HeadsetHalConstants.CALL_STATE_DIALING;
+ else
+ call_state = mStateMachineCallState.mCallState;
+ log("sending call state in CIND resp as " + call_state);
+
+ /* Some Handsfree devices or carkits expect the +CIND to be properly
+ responded with the correct service availablity and signal strength,
+ while the regular call is active or held or in progress.*/
+ if(((!mHeadsetService.isVirtualCallStarted()) &&
+ (mStateMachineCallState.mNumActive > 0) || (mStateMachineCallState.mNumHeld > 0) ||
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING ||
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING ||
+ mStateMachineCallState.mCallState == HeadsetHalConstants.CALL_STATE_INCOMING) &&
+ (phoneState.getCindService() == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE)) {
+ log("processAtCind: If regular call is in process/active/held while RD connection " +
+ "during BT-ON, update service availablity and signal strength");
+ service = HeadsetHalConstants.NETWORK_STATE_AVAILABLE;
+ signal = 3;
+ } else {
+ service = phoneState.getCindService();
+ signal = phoneState.getCindSignal();
}
- mNativeInterface.cindResponse(device, phoneState.getCindService(), call, callSetup,
- phoneState.getCallState(), phoneState.getCindSignal(), phoneState.getCindRoam(),
+ mNativeInterface.cindResponse(device, service, call, callSetup,
+ call_state, signal, phoneState.getCindRoam(),
phoneState.getCindBatteryCharge());
+ log("Exit processAtCind()");
}
private void processAtCops(BluetoothDevice device) {
String operatorName = mSystemInterface.getNetworkOperator();
- if (operatorName == null) {
- operatorName = "";
+ if (operatorName == null || operatorName.equals("")) {
+ operatorName = "No operator";
}
mNativeInterface.copsResponse(device, operatorName);
}
@@ -1656,12 +2325,21 @@
private void processAtClcc(BluetoothDevice device) {
if (mHeadsetService.isVirtualCallStarted()) {
// In virtual call, send our phone number instead of remote phone number
- String phoneNumber = mSystemInterface.getSubscriberNumber();
+ // some carkits cross-check subscriber number( fetched by AT+CNUM) against
+ // number sent in clcc and reject sco connection.
+ String phoneNumber = VOIP_CALL_NUMBER;
if (phoneNumber == null) {
phoneNumber = "";
}
int type = PhoneNumberUtils.toaFromString(phoneNumber);
- mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
+ log(" processAtClcc phonenumber = "+ phoneNumber + " type = " + type);
+ // call still in dialling or alerting state
+ if (mStateMachineCallState.mNumActive == 0) {
+ mNativeInterface.clccResponse(device, 1, 0, mStateMachineCallState.mCallState, 0,
+ false, phoneNumber, type);
+ } else {
+ mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
+ }
mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
} else {
// In Telecom call, ask Telecom to send send remote phone number
@@ -1821,6 +2499,8 @@
processAtCpbs(atCommand.substring(5), commandType, device);
} else if (atCommand.startsWith("+CPBR")) {
processAtCpbr(atCommand.substring(5), commandType, device);
+ } else if (atCommand.startsWith("+CSQ")) {
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
} else {
processVendorSpecificAt(atCommand, device);
}
@@ -1830,19 +2510,24 @@
private void processKeyPressed(BluetoothDevice device) {
if (mSystemInterface.isRinging()) {
mSystemInterface.answerCall(device);
- } else if (mSystemInterface.isInCall()) {
- if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- // Should connect audio as well
- if (!mHeadsetService.setActiveDevice(mDevice)) {
- Log.w(TAG, "processKeyPressed, failed to set active device to " + mDevice);
- }
- } else {
- mSystemInterface.hangupCall(device);
- }
} else if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
if (!mNativeInterface.disconnectAudio(mDevice)) {
Log.w(TAG, "processKeyPressed, failed to disconnect audio from " + mDevice);
}
+ } else if (mSystemInterface.isInCall()) {
+ if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ // Should connect audio as well
+ if (mDevice.equals(mHeadsetService.getActiveDevice())) {
+ Log.w(TAG, "processKeyPressed: device "+ mDevice+" is active, create SCO");
+ mNativeInterface.connectAudio(mDevice);
+ } else {
+ //Set active device and create SCO
+ if (!mHeadsetService.setActiveDevice(mDevice)) {
+ Log.w(TAG, "processKeyPressed, failed to set active device to "
+ + mDevice);
+ }
+ }
+ }
} else {
// We have already replied OK to this HSP command, no feedback is needed
if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
@@ -1918,6 +2603,41 @@
sendIndicatorIntent(device, indId, indValue);
}
+ private void processCpbr(Intent intent)
+ {
+ int atCommandResult = 0;
+ int atCommandErrorCode = 0;
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ log("Enter processCpbr()");
+ // ASSERT: (headset != null) && headSet.isConnected()
+ // REASON: mCheckingAccessPermission is true, otherwise resetAtState
+ // has set mCheckingAccessPermission to false
+ if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+ if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+ BluetoothDevice.CONNECTION_ACCESS_NO)
+ == BluetoothDevice.CONNECTION_ACCESS_YES) {
+ if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ }
+ atCommandResult = mPhonebook.processCpbrCommand(device);
+ } else {
+ if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+ mDevice.setPhonebookAccessPermission(
+ BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+ }
+ mPhonebook.setCpbrIndex(-1);
+ mPhonebook.setCheckingAccessPermission(false);
+
+ if (atCommandResult >= 0) {
+ mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
+ } else {
+ log("processCpbr - RESULT_NONE");
+ }
+ Log.d(TAG, "Exit processCpbr()");
+ }
+
private void processSendClccResponse(HeadsetClccResponse clcc) {
if (!hasMessages(CLCC_RSP_TIMEOUT)) {
return;
@@ -1925,8 +2645,24 @@
if (clcc.mIndex == 0) {
removeMessages(CLCC_RSP_TIMEOUT);
}
- mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
- clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+ // get the top of the Q
+ HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
+
+ /* Send call state DIALING if call alerting update is still in the Q */
+ if (clcc.mStatus == HeadsetHalConstants.CALL_STATE_ALERTING &&
+ tempCallState != null &&
+ tempCallState.mCallState == HeadsetHalConstants.CALL_STATE_ALERTING) {
+ log("sending call status as DIALING");
+ mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection,
+ HeadsetHalConstants.CALL_STATE_DIALING,
+ clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+ } else {
+ log("sending call status as " + clcc.mStatus);
+ mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection,
+ clcc.mStatus,
+ clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+ }
+ log("Exit processSendClccResponse()");
}
private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
@@ -1963,6 +2699,50 @@
mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice, events);
}
+ boolean isConnectedDeviceBlacklistedforIncomingCall() {
+ // Checking for the Blacklisted device Addresses
+ for (int j = 0; j < BlacklistDeviceAddrToDelayCallInd.length;j++) {
+ String addr = BlacklistDeviceAddrToDelayCallInd[j];
+ if (mDevice.toString().toLowerCase().startsWith(addr.toLowerCase())) {
+ log("Remote device address Blacklisted for sending delay");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean isDeviceBlacklistedForSendingCallIndsBackToBack() {
+ // Checking for the Blacklisted device Addresses
+ for (int j = 0; j < BlacklistDeviceForSendingVOIPCallIndsBackToBack.length;j++) {
+ String addr = BlacklistDeviceForSendingVOIPCallIndsBackToBack[j];
+ if (mDevice.toString().toLowerCase().startsWith(addr.toLowerCase())) {
+ Log.w(TAG, "Remote device " + mDevice +
+ " address Blacklisted for sending VOIP call inds back to back");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void sendVoipConnectivityNetworktype(boolean isVoipStarted) {
+ Log.d(TAG, "Enter sendVoipConnectivityNetworktype()");
+ NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
+ if (networkInfo == null || !networkInfo.isAvailable() || !networkInfo.isConnected()) {
+ Log.d(TAG, "No connected/available connectivity network, don't update soc");
+ return;
+ }
+
+ if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
+ Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_MOBILE, don't update to soc");
+ } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_WIFI, update n/w type & start/stop to soc");
+ mAdapterService.voipNetworkWifiInfo(isVoipStarted, true);
+ } else {
+ Log.d(TAG, "Voip/VoLTE started/stopped on some other n/w, don't update to soc");
+ }
+ Log.d(TAG, "Exit sendVoipConnectivityNetworktype()");
+ }
+
@Override
protected void log(String msg) {
if (DBG) {
@@ -1990,38 +2770,23 @@
}
private void handleAccessPermissionResult(Intent intent) {
- log("handleAccessPermissionResult");
+ log("Enter handleAccessPermissionResult");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (!mPhonebook.getCheckingAccessPermission()) {
- return;
- }
- int atCommandResult = 0;
- int atCommandErrorCode = 0;
- // HeadsetBase headset = mHandsfree.getHeadset();
- // ASSERT: (headset != null) && headSet.isConnected()
- // REASON: mCheckingAccessPermission is true, otherwise resetAtState
- // has set mCheckingAccessPermission to false
- if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
- if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
- BluetoothDevice.CONNECTION_ACCESS_NO)
- == BluetoothDevice.CONNECTION_ACCESS_YES) {
- if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- }
- atCommandResult = mPhonebook.processCpbrCommand(device);
- } else {
- if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- }
+ if (mPhonebook != null) {
+ if (!mPhonebook.getCheckingAccessPermission()) {
+ return;
+ }
+
+ Message m = obtainMessage(PROCESS_CPBR);
+ m.obj = intent;
+ sendMessage(m);
+ } else {
+ Log.e(TAG, "Phonebook handle null");
+ if (device != null) {
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
}
}
- mPhonebook.setCpbrIndex(-1);
- mPhonebook.setCheckingAccessPermission(false);
- if (atCommandResult >= 0) {
- mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
- } else {
- log("handleAccessPermissionResult - RESULT_NONE");
- }
+ log("Exit handleAccessPermissionResult()");
}
private static String getMessageName(int what) {
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 81090eb..2593adf 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -108,6 +108,11 @@
// Synchronization should make sure unbind can be successful
mHeadsetService.unbindService(mPhoneProxyConnection);
}
+ //sometimes when BT is turned off while in call,
+ // we don't get a chance to move out of AudioOn state
+
+ mAudioManager.setParameters("BT_SCO=off");
+ mAudioManager.setBluetoothScoOn(false);
mHeadsetPhoneState.cleanup();
}
@@ -329,8 +334,7 @@
@VisibleForTesting
public boolean isInCall() {
return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
- > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
- && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
+ > 0) || (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE));
}
/**
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index ff1a608..05c7476 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -46,7 +46,7 @@
* @hide
*/
public class HidHostService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "BluetoothHidHostService";
private Map<BluetoothDevice, Integer> mInputDevices;
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
index cb9481b..aa5b969 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
@@ -40,10 +40,12 @@
public final String mBase_uri_no_account;
private final String mUci;
private final String mUciPrefix;
+ private String mDisplayName;
+ private String mEmailAddress;
public BluetoothMapAccountItem(String id, String name, String packageName, String authority,
Drawable icon, BluetoothMapUtils.TYPE appType, String uci, String uciPrefix) {
- this.mName = name;
+ this.mName = name + " Server " + id;
this.mIcon = icon;
this.mPackageName = packageName;
this.mId = id;
@@ -228,4 +230,23 @@
return mType;
}
+ public void setDisplayName(String name) {
+ mDisplayName = name;
+ if(V) Log.v(TAG, "setDispName: " + mDisplayName );
+ }
+
+ public void setEmailAddress(String emailId) {
+ mEmailAddress = emailId;
+ if(V) Log.v(TAG, "setOrgEmail: " + mEmailAddress );
+ }
+
+ public String getDisplayName() {
+ if(V) Log.v(TAG, "getDispName: " + mDisplayName );
+ return mDisplayName;
+ }
+
+ public String getEmailAddress() {
+ if(V) Log.v(TAG, "getOrgEmail: " + mDisplayName );
+ return mEmailAddress;
+ }
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
index b4dfe5a..7ed461e 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -23,10 +23,13 @@
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.SystemProperties;
import android.util.Log;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.mapapi.BluetoothMapContract;
-
+import com.android.bluetooth.R;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -59,9 +62,24 @@
public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
mContext = context;
mMapService = mapService;
- mResolver = context.getContentResolver();
- mLoader = new BluetoothMapAccountLoader(mContext);
- mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
+ mResolver = context.getContentResolver();
+ AdapterService adapterService = AdapterService.getAdapterService();
+ boolean isEmailSupported = false;
+
+ if (adapterService != null) {
+ isEmailSupported = adapterService.getProfileInfo(AbstractionLayer.MAP, AbstractionLayer.MAP_EMAIL_SUPPORT);
+ Log.d(TAG, "isEmailSupported: " + isEmailSupported);
+ }
+ if (isEmailSupported) {
+ mLoader = new BluetoothMapAccountEmailLoader(mContext);
+ } else {
+ mLoader = new BluetoothMapAccountLoader(mContext);
+ }
+ mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
+ if (isEmailSupported) {
+ SystemProperties.set("vendor.bluetooth.emailaccountcount",
+ String.valueOf(mLoader.getAccountsEnabledCount()));
+ }
createReceiver();
initObservers();
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index c862619..70e797d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -588,7 +588,7 @@
}
}
- private class Event {
+ class Event {
public String eventType;
public long handle;
public String folder = null;
@@ -835,6 +835,8 @@
public long folderId = -1; // Email folder ID
public long oldFolderId = -1; // Used for email undelete
public boolean localInitiatedSend = false; // Used for MMS to filter out events
+ boolean localInitiatedReadStatus = false; // Used for SetMsgStatusRead to filter out event
+ boolean localInitiatedShift = false; // Used for SetMsgStatusDelete to filter out events
public boolean transparent = false;
// Used for EMAIL to delete message sent with transparency
public int flagRead = -1; // Message status read/unread
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 560558e..6867b56 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -40,6 +40,7 @@
private String mName;
private BluetoothMapFolderElement mParent = null;
private long mFolderId = -1;
+ private int mFolderType = -1;
private boolean mHasSmsMmsContent = false;
private boolean mHasImContent = false;
private boolean mHasEmailContent = false;
@@ -128,6 +129,17 @@
return sb.toString();
}
+ public void setFolderType(int folderType) {
+ this.mFolderType = folderType;
+ }
+
+ /**
+ * Return folder typ info
+ * @return a integer for email type.
+ */
+ public int getFolderType() {
+ return mFolderType;
+ }
public BluetoothMapFolderElement getFolderByName(String name) {
BluetoothMapFolderElement folderElement = this.getRoot();
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index e3df91f..9d1d355 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -287,9 +287,12 @@
mAcceptNewConnections = true;
} else {
- mServerSockets = ObexServerSockets.create(this);
- mAcceptNewConnections = true;
+ mServerSockets = ObexServerSockets.createWithFixedChannels(this,
+ (SdpManager.MAP_RFCOMM_CHANNEL +
+ SdpManager.NEXT_RFCOMM_CHANNEL * mMasInstanceId),
+ (SdpManager.MAP_L2CAP_PSM + SdpManager.NEXT_L2CAP_CHANNEL * mMasInstanceId));
+ mAcceptNewConnections = true;
if (mServerSockets == null) {
// TODO: Handle - was not handled before
Log.e(mTag, "Failed to start the listeners");
@@ -361,12 +364,27 @@
mMnsClient = mnsClient;
BluetoothMapObexServer mapServer;
- mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount,
- mEnableSmsMms);
+ if (mAccount != null && mAccount.getType() == TYPE.EMAIL) {
+ Log.d(mTag, "startObexServerSession getType = " + mAccount.getType());
+ mObserver = new BluetoothMapContentObserverEmail(mContext,
+ mMnsClient,
+ this,
+ mAccount,
+ mEnableSmsMms);
+ } else {
+ mObserver = new BluetoothMapContentObserver(mContext,
+ mMnsClient,
+ this,
+ mAccount,
+ mEnableSmsMms);
+ }
mObserver.init();
- mapServer =
- new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount,
- mEnableSmsMms);
+ mapServer = new BluetoothMapObexServer(mServiceHandler,
+ mContext,
+ mObserver,
+ this,
+ mAccount,
+ mEnableSmsMms);
// setup transport
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
@@ -398,7 +416,7 @@
return (mConnSocket != null);
}
- public void shutdown() {
+ public synchronized void shutdown() {
if (D) {
Log.d(mTag, "MAP Service shutdown");
}
@@ -432,6 +450,7 @@
private synchronized void closeServerSockets(boolean block) {
+ if(V) Log.d(mTag, "closeServerSock");
// exit SocketAcceptThread early
ObexServerSockets sockets = mServerSockets;
if (sockets != null) {
@@ -441,6 +460,7 @@
}
private synchronized void closeConnectionSocket() {
+ if(V) Log.d(mTag, "closeConnectionSock");
if (mConnSocket != null) {
try {
mConnSocket.close();
@@ -452,11 +472,11 @@
}
}
- public void setRemoteFeatureMask(int supportedFeatures) {
- if (V) {
- Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
- }
- mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES;
+ public void setRemoteFeatureMask(int supportedFeatures, int remoteProfileVersion) {
+ Log.d(mTag, "Current Feature Mask: " + Integer.toHexString(mRemoteFeatureMask)
+ + ", remote feature Mask: " + Integer.toHexString(supportedFeatures));
+ mRemoteFeatureMask = remoteProfileVersion > SDP_MAP_MAS_VERSION ?
+ SDP_MAP_MAS_FEATURES : supportedFeatures;
if (mObserver != null) {
mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
if (V) {
@@ -477,6 +497,7 @@
/* Signal to the service that we have received an incoming connection.
*/
boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
+ if(V) Log.d(mTag, "onConnect");
if (isValid) {
mRemoteDevice = device;
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index afd1eb0..dd8dd48 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -32,6 +32,7 @@
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapEmailContract;
import java.io.IOException;
import java.io.InputStream;
@@ -156,12 +157,17 @@
}
mProviderClient = acquireUnstableContentProviderOrThrow();
}
+ if (account != null && account.getType() == TYPE.EMAIL) {
+ mOutContent = new BluetoothMapContentEmail(mContext, mAccount, mMasInstance);
+ } else {
+ mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
+ }
buildFolderStructure(); /* Build the default folder structure, and set
mCurrentFolder to root folder */
mObserver.setFolderStructure(mCurrentFolder.getRoot());
- mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
+
}
@@ -214,12 +220,12 @@
if (mEnableSmsMms) {
addSmsMmsFolders(tmpFolder);
}
- if (hasEmail) {
- if (D) {
- Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
- }
- addEmailFolders(tmpFolder);
+
+ if (hasEmail && (mOutContent instanceof BluetoothMapContentEmail)) {
+ if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
+ ((BluetoothMapContentEmail)mOutContent).addEmailFolders(tmpFolder);
}
+
if (hasIM) {
addImFolders(tmpFolder);
}
@@ -474,6 +480,11 @@
if (V) {
Log.d(TAG, "TYPE_MESSAGE_UPDATE:");
}
+ if (mAccount!= null && mAccount.getType() == TYPE.EMAIL &&
+ (mOutContent instanceof BluetoothMapContentEmail)) {
+ ((BluetoothMapContentEmail)mOutContent).msgUpdate();
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
return updateInbox();
} else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
if (V) {
@@ -660,7 +671,8 @@
folderName = folderElement.getName();
}
if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) && !folderName
- .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+ .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT) &&
+ !folderName.equalsIgnoreCase(BluetoothMapEmailContract.FOLDER_NAME_DRAFTS)) {
if (D) {
Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " + "folderName="
+ folderName);
@@ -783,6 +795,7 @@
long handle;
BluetoothMapUtils.TYPE msgType;
+ if (D) Log.d(TAG, "setMessageStatus():");
if (msgHandle == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
@@ -948,10 +961,16 @@
mCurrentFolder = mCurrentFolder.getRoot();
}
} else {
+ if (mAccount!= null && mAccount.getType() == TYPE.EMAIL) {
+ if (folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+ folderName = BluetoothMapEmailContract.FOLDER_NAME_DRAFTS;
+ }
+ }
folder = mCurrentFolder.getSubFolder(folderName);
if (folder != null) {
mCurrentFolder = folder;
} else {
+ Log.w(TAG,"SubFolder Not found! : " + mCurrentFolder.getName());
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
}
@@ -1519,8 +1538,20 @@
if (maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
maxListCount = 1024;
}
-
- if (maxListCount != 0) {
+ try {
+ if ( (mAccount != null && mAccount.getType() == TYPE.EMAIL) &&
+ (mOutContent instanceof BluetoothMapContentEmail) &&
+ !mCurrentFolder.getName().equals("telecom") &&
+ !mCurrentFolder.getName().equals("root")) {
+ if (D) Log.d(TAG, "RefreshFolderStructure(): " + mCurrentFolder.getName());
+ ((BluetoothMapContentEmail)mOutContent).addEmailFolders(mCurrentFolder);
+ }
+ } catch( RemoteException e1) {
+ Log.v(TAG,"sendFolderList Refresh failed : Go with existing for :"
+ + mCurrentFolder.getName());
+ }
+ if(maxListCount != 0)
+ {
outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
} else {
// ESR08 specified that this shall only be included for MaxListCount=0
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index bfb1d3f..4248435 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -29,6 +29,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
+import android.Manifest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -36,6 +37,7 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
@@ -56,7 +58,7 @@
public class BluetoothMapService extends ProfileService {
private static final String TAG = "BluetoothMapService";
-
+ private static final String LOG_TAG = "BluetoothMap";
/**
* To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
* restart com.android.bluetooth process. only enable DEBUG log:
@@ -66,7 +68,7 @@
public static final boolean DEBUG = true; //FIXME set to false;
- public static final boolean VERBOSE = false;
+ public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
/**
* Intent indicating timeout for user confirmation, which is sent to
@@ -97,8 +99,8 @@
private static final int USER_TIMEOUT = 2;
private static final int DISCONNECT_MAP = 3;
private static final int SHUTDOWN = 4;
- private static final int UPDATE_MAS_INSTANCES = 5;
-
+ private static final int UPDATE_MAS_INSTANCES = 6;
+ private static final int CREATE_MAS_INSTANCES = 5;
private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
private PowerManager.WakeLock mWakeLock = null;
@@ -123,11 +125,11 @@
// The remote connected device - protect access
private static BluetoothDevice sRemoteDevice = null;
- private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
+ ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
private static String sRemoteDeviceName = null;
private int mState;
- private BluetoothMapAppObserver mAppObserver = null;
+ BluetoothMapAppObserver mAppObserver = null;
private AlarmManager mAlarmManager = null;
private boolean mIsWaitingAuthorization = false;
@@ -195,7 +197,7 @@
}
mSessionStatusHandler = null;
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "MAP Service closeService out");
}
}
@@ -268,7 +270,7 @@
mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
RELEASE_WAKE_LOCK_DELAY);
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "startObexServerSessions() success!");
}
}
@@ -283,7 +285,7 @@
*/
private void stopObexServerSessions(int masId) {
if (DEBUG) {
- Log.d(TAG, "MAP Service STOP ObexServerSessions()");
+ Log.d(TAG, "MAP Service STOP ObexServerSessions() masId: " + masId);
}
boolean lastMasInst = true;
@@ -298,9 +300,13 @@
} // Else just close down it all
// Shutdown the MNS client - this must happen before MAS close
- if (mBluetoothMnsObexClient != null && lastMasInst) {
- mBluetoothMnsObexClient.shutdown();
- mBluetoothMnsObexClient = null;
+ if (mBluetoothMnsObexClient != null ) {
+ if (lastMasInst) {
+ mBluetoothMnsObexClient.shutdown();
+ mBluetoothMnsObexClient = null;
+ } else if(masId != -1 && mMasInstances != null) {
+ BluetoothMapFixes.handleMnsShutdown(mMasInstances,masId);
+ }
}
BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
@@ -333,17 +339,23 @@
}
private final class MapServiceMessageHandler extends Handler {
- private MapServiceMessageHandler(Looper looper) {
+ Context mContxt;
+ private MapServiceMessageHandler(Context contxt, Looper looper) {
super(looper);
+ mContxt = contxt;
}
@Override
public void handleMessage(Message msg) {
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "Handler(): got msg=" + msg.what);
}
switch (msg.what) {
+ case CREATE_MAS_INSTANCES:
+ Log.d(TAG, "CREATE_MAS_INSTANCES ");
+ BluetoothMapFixes.createMasInstances(BluetoothMapService.this);
+ break;
case UPDATE_MAS_INSTANCES:
updateMasInstancesHandler();
break;
@@ -600,7 +612,7 @@
HandlerThread thread = new HandlerThread("BluetoothMapHandler");
thread.start();
Looper looper = thread.getLooper();
- mSessionStatusHandler = new MapServiceMessageHandler(looper);
+ mSessionStatusHandler = new MapServiceMessageHandler(this, looper);
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
@@ -618,17 +630,26 @@
Log.e(TAG, "Wrong mime type!!!", e);
}
if (!mRegisteredMapReceiver) {
- registerReceiver(mMapReceiver, filter);
- registerReceiver(mMapReceiver, filterMessageSent);
- mRegisteredMapReceiver = true;
+ try {
+ registerReceiver(mMapReceiver, filter);
+ // We need WRITE_SMS permission to handle messages in
+ // actionMessageSentDisconnected()
+ registerReceiver(mMapReceiver, filterMessageSent,
+ Manifest.permission.WRITE_SMS, null);
+ mRegisteredMapReceiver = true;
+ } catch (Exception e) {
+ Log.e(TAG,"Unable to register map receiver",e);
+ }
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
- mAppObserver = new BluetoothMapAppObserver(this, this);
+
+ // Move SDP records creation to Handler Thread instead of main thread.
+ BluetoothMapFixes.sendCreateMasInstances(this, CREATE_MAS_INSTANCES);
mSmsCapable = getResources().getBoolean(com.android.internal.R.bool.config_sms_capable);
-
- mEnabledAccounts = mAppObserver.getEnabledAccountItems();
- createMasInstances(); // Uses mEnabledAccounts
+ if (DEBUG) {
+ Log.d(TAG, "mSmsCapable :" + mSmsCapable);
+ }
sendStartListenerMessage(-1);
setBluetoothMapService(this);
@@ -690,8 +711,9 @@
if (DEBUG) {
Log.d(TAG, "updateMasInstancesHandler() state = " + getState());
}
-
- if (getState() != BluetoothMap.STATE_DISCONNECTED) {
+ if (BluetoothMapFixes.checkMapAppObserver(mAppObserver))
+ return;
+ if (getState() == BluetoothMap.STATE_CONNECTING) {
mAccountChanged = true;
return;
}
@@ -699,6 +721,10 @@
ArrayList<BluetoothMapAccountItem> newAccountList = mAppObserver.getEnabledAccountItems();
ArrayList<BluetoothMapAccountItem> newAccounts = new ArrayList<>();
+ Log.d(TAG, "new Account List size = " + newAccountList.size());
+ SystemProperties.set("vendor.bluetooth.emailaccountcount",
+ String.valueOf(newAccountList.size()));
+
for (BluetoothMapAccountItem account : newAccountList) {
if (!mEnabledAccounts.remove(account)) {
newAccounts.add(account);
@@ -776,7 +802,7 @@
return 0xff; // This will never happen, as we only allow 10 e-mail accounts to be enabled
}
- private void createMasInstances() {
+ void createMasInstances() {
int masId = MAS_ID_SMS_MMS;
if (mSmsCapable) {
@@ -788,13 +814,15 @@
masId++;
}
- // get list of accounts already set to be visible through MAP
- for (BluetoothMapAccountItem account : mEnabledAccounts) {
- BluetoothMapMasInstance newInst =
- new BluetoothMapMasInstance(this, this, account, masId, false);
- mMasInstances.append(masId, newInst);
- mMasInstanceMap.put(account, newInst);
- masId++;
+ if (mEnabledAccounts != null) {
+ // get list of accounts already set to be visible through MAP
+ for (BluetoothMapAccountItem account : mEnabledAccounts) {
+ BluetoothMapMasInstance newInst =
+ new BluetoothMapMasInstance(this, this, account, masId, false);
+ mMasInstances.append(masId, newInst);
+ mMasInstanceMap.put(account, newInst);
+ masId++;
+ }
}
}
@@ -814,8 +842,10 @@
if (mRegisteredMapReceiver) {
mRegisteredMapReceiver = false;
unregisterReceiver(mMapReceiver);
- mAppObserver.shutdown();
+ if (mAppObserver != null)
+ mAppObserver.shutdown();
}
+ setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
sendShutdownMessage();
return true;
}
@@ -851,7 +881,7 @@
sRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
mSdpSearchInitiated = true;
}
- } else if (!sRemoteDevice.equals(remoteDevice)) {
+ } else if (!remoteDevice.equals(sRemoteDevice)) {
Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + (
(remoteDevice == null) ? "unknown" : remoteDevice.getName()));
return false;
@@ -955,6 +985,7 @@
}
private void sendShutdownMessage() {
+ if (VERBOSE) Log.d(TAG, "sendShutdownMessage() In");
// Pending messages are no longer valid. To speed up things, simply delete them.
if (mRemoveTimeoutMsg) {
Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
@@ -1068,7 +1099,8 @@
if (status != -1 && mMnsRecord != null) {
for (int i = 0, c = mMasInstances.size(); i < c; i++) {
mMasInstances.valueAt(i)
- .setRemoteFeatureMask(mMnsRecord.getSupportedFeatures());
+ .setRemoteFeatureMask(mMnsRecord.getSupportedFeatures(),
+ mMnsRecord.getProfileVersion());
}
}
if (mSdpSearchInitiated) {
@@ -1097,23 +1129,27 @@
}
if (!handled) {
// Move the SMS to the correct folder.
- BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent,
- result);
+ try {
+ BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent,
+ result);
+ } catch(IllegalArgumentException e) {
+ return;
+ }
}
- } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
- && mIsWaitingAuthorization) {
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (sRemoteDevice == null || device == null) {
- Log.e(TAG, "Unexpected error!");
+ Log.i(TAG, "sRemoteDevice :" + sRemoteDevice + " device:" + device);
return;
}
if (VERBOSE) {
- Log.v(TAG, "ACL disconnected for " + device);
+ Log.v(TAG, "ACL disconnected for " + device
+ + " mIsWaitingAuthorization :" + mIsWaitingAuthorization);
}
- if (sRemoteDevice.equals(device)) {
+ if (mIsWaitingAuthorization && device.equals(sRemoteDevice)) {
// Send any pending timeout now, since ACL got disconnected
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
@@ -1282,7 +1318,8 @@
println(sb, "mRemoteDevice: " + sRemoteDevice);
println(sb, "sRemoteDeviceName: " + sRemoteDeviceName);
println(sb, "mState: " + mState);
- println(sb, "mAppObserver: " + mAppObserver);
+ if (mAppObserver != null)
+ println(sb, "mAppObserver: " + mAppObserver);
println(sb, "mIsWaitingAuthorization: " + mIsWaitingAuthorization);
println(sb, "mRemoveTimeoutMsg: " + mRemoveTimeoutMsg);
println(sb, "mPermission: " + mPermission);
@@ -1293,8 +1330,10 @@
println(sb, " " + key + " : " + mMasInstanceMap.get(key));
}
println(sb, "mEnabledAccounts:");
- for (BluetoothMapAccountItem account : mEnabledAccounts) {
- println(sb, " " + account);
+ if (mEnabledAccounts != null) {
+ for (BluetoothMapAccountItem account : mEnabledAccounts) {
+ println(sb, " " + account);
+ }
}
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index e222aa9..55e5933 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -30,6 +30,7 @@
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import com.android.bluetooth.map.BluetoothMapCommonUtils.BMsgReaderExt;
public abstract class BluetoothMapbMessage {
protected static final String TAG = "BluetoothMapbMessage";
@@ -58,7 +59,9 @@
private ArrayList<VCard> mOriginator = null;
private ArrayList<VCard> mRecipient = null;
-
+ private ArrayList<String> mRecipientTo = null;
+ private ArrayList<String> mRecipientCc = null;
+ private ArrayList<String> mRecipientBCc = null;
public static class VCard {
/* VCARD attributes */
@@ -194,6 +197,13 @@
}
}
+ public String[] getEmailAddresses() {
+ if (mEmailAddresses.length > 0) {
+ return mEmailAddresses;
+ } else
+ throw new IllegalArgumentException("No Recipient Email Address");
+ }
+
public String getFirstBtUci() {
if (mBtUcis.length > 0) {
return mBtUcis[0];
@@ -279,6 +289,7 @@
}
// Empty phone number - ignore
} else if (line.startsWith("EMAIL:")) {
+ line = line.replace("<", "<").replace(">", ">");
parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
if (parts.length == 2) {
String[] subParts = parts[1].split("[^\\\\];");
@@ -323,7 +334,7 @@
;
- private static class BMsgReader {
+ protected static class BMsgReader {
InputStream mInStream;
BMsgReader(InputStream is) {
@@ -371,15 +382,14 @@
/**
* Read a line of text from the BMessage.
* @return the next line of text, or null at end of file, or if UTF-8 is not supported.
+ * @hide
*/
public String getLine() {
try {
byte[] line = getLineAsBytes();
- if (line.length == 0) {
+ if (BluetoothMapFixes.isLastLine(line))
return null;
- } else {
- return new String(line, "UTF-8");
- }
+ return new String(line, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.w(TAG, e);
return null;
@@ -485,7 +495,7 @@
public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset)
throws IllegalArgumentException {
- BMsgReader reader;
+ BMsgReaderExt reader;
String line = "";
BluetoothMapbMessage newBMsg = null;
boolean status = false;
@@ -561,7 +571,7 @@
Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
} /* End of if(V) log-section */
- reader = new BMsgReader(bMsgStream);
+ reader = new BluetoothMapCommonUtils.BMsgReaderExt(bMsgStream);
reader.expect("BEGIN:BMSG");
reader.expect("VERSION");
@@ -610,7 +620,7 @@
newBMsg = new BluetoothMapbMessageMime();
break;
case EMAIL:
- newBMsg = new BluetoothMapbMessageEmail();
+ newBMsg = new BluetoothMapbMessageExtEmail();
break;
case IM:
newBMsg = new BluetoothMapbMessageMime();
@@ -654,6 +664,10 @@
}
if (line.contains("BEGIN:BENV")) {
newBMsg.parseEnvelope(reader, 0);
+ if ( type == TYPE.EMAIL && newBMsg instanceof BluetoothMapbMessageExtEmail) {
+ ((BluetoothMapbMessageExtEmail)newBMsg)
+ .parseBodyEmail(reader.getLastStringTerminator("END:BBODY"));
+ }
} else {
throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
}
@@ -697,7 +711,7 @@
}
parseEnvelope(reader, ++level); // Nested BENV
}
- if (line.contains("BEGIN:BBODY")) {
+ if (mType != TYPE.EMAIL && line.contains("BEGIN:BBODY")) {
if (D) {
Log.d(TAG, "Decoding bbody");
}
@@ -1064,4 +1078,26 @@
return null;
}
}
+ public ArrayList<String> getmRecipientTo() {
+ return mRecipientTo;
+ }
+
+ public void setmRecipientTo(ArrayList<String> mRecipientTo) {
+ this.mRecipientTo = mRecipientTo;
+ }
+ public ArrayList<String> getmRecipientCc() {
+ return mRecipientCc;
+ }
+
+ public void setmRecipientCc(ArrayList<String> mRecipientCc) {
+ this.mRecipientCc = mRecipientCc;
+ }
+
+ public ArrayList<String> getmRecipientBCc() {
+ return mRecipientBCc;
+ }
+
+ public void setmRecipientBCc(ArrayList<String> mRecipientBCc) {
+ this.mRecipientBCc = mRecipientBCc;
+ }
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index a247bff..1101ba0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -153,7 +153,7 @@
private ArrayList<Rfc822Token> mBcc = null; // Can be empty
private ArrayList<Rfc822Token> mReplyTo = null; // Can be empty
private String mMessageId = null;
- private ArrayList<MimePart> mParts = null;
+ ArrayList<MimePart> mParts = null;
private String mContentType = null;
private String mBoundary = null;
private boolean mTextonly = false;
@@ -161,7 +161,7 @@
private boolean mHasHeaders = false;
private String mMyEncoding = null;
- private String getBoundary() {
+ String getBoundary() {
// Include "=_" as these cannot occur in quoted printable text
if (mBoundary == null) {
mBoundary = "--=_" + UUID.randomUUID();
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index eba6485..2f2af1d 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -222,7 +222,6 @@
if (looper != null) {
looper.quit();
}
- mHandler = null;
}
/* Disconnect if connected */
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index c1ab39e..95f937f 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -59,7 +59,8 @@
}
sContext = context;
sAcceptThread = new SocketAcceptor();
- sServerSockets = ObexServerSockets.create(sAcceptThread);
+ sServerSockets = ObexServerSockets.createWithFixedChannels(sAcceptThread,
+ SdpManager.MNS_RFCOMM_CHANNEL, SdpManager.MNS_L2CAP_PSM);
SdpManager sdpManager = SdpManager.getDefaultManager();
if (sdpManager == null) {
Log.e(TAG, "SdpManager is null");
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
index c7029ce..7a1c5da 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
@@ -31,6 +31,7 @@
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
@@ -50,6 +51,7 @@
private static final int AVRCP_MAX_VOL = 127;
private static int sDeviceMaxVolume = 0;
+ private AdapterService mAdapterService;
private MediaPlayerList mMediaPlayerList;
private AudioManager mAudioManager;
private AvrcpBroadcastReceiver mReceiver;
@@ -139,12 +141,19 @@
Log.i(TAG, "Starting the AVRCP Target Service");
mCurrentData = new MediaData(null, null, null);
+ mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+ "AdapterService cannot be null when A2dpService starts");
mReceiver = new AvrcpBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
registerReceiver(mReceiver, filter);
+ if(mAdapterService.isVendorIntfEnabled()) {
+ Log.i(TAG, "Vendor Stack is enabled, using legacy implementation");
+ SystemProperties.set(AVRCP_ENABLE_PROPERTY, "false");
+ }
+
if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
sInstance = null;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index a66a667..824ded6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -150,6 +150,7 @@
BluetoothOppShareInfo info = mShares.get(i);
if (info.mStatus < 200) {
+ BTOppUtils.updateFileNameInDb(mContext, info);
if (info.mDirection == BluetoothShare.DIRECTION_INBOUND && info.mFilename != null) {
new File(info.mFilename).delete();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
index 0253e24..42da777 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
@@ -106,7 +106,9 @@
super.onPause();
if (!mOppManager.mSendingFlag) {
+ BluetoothOppManager.isReadyForFileSharing = false;
mOppManager.cleanUpSendingFileInfo();
+ BluetoothOppManager.isReadyForFileSharing = true;
}
}
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
index 6e83480..9a81ce8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -32,7 +32,8 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
+ if(D) Log.d(TAG, " Action :" + action);
+ if (action == null) return;
if (action.equals(Constants.ACTION_HANDOVER_SEND) || action.equals(
Constants.ACTION_HANDOVER_SEND_MULTIPLE)) {
final BluetoothDevice device =
diff --git a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
index 1b5ffe1..8eda73c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
@@ -151,7 +151,7 @@
mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_CONFIRMED);
this.getContentResolver().update(mUri, mUpdateValues, null, null);
-
+ if (D) Log.v(TAG, " Confirmed :" + mUri);
Toast.makeText(this, getString(R.string.bt_toast_1), Toast.LENGTH_SHORT).show();
}
break;
@@ -162,6 +162,7 @@
mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_DENIED);
this.getContentResolver().update(mUri, mUpdateValues, null, null);
+ if (D) Log.v(TAG, " Denied :" + mUri);
break;
}
}
@@ -222,7 +223,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS_TIMEOUT_DIALOG:
- if (V) {
+ if (D) {
Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
}
finish();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 99e3e69..d952478 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -61,7 +61,7 @@
* selection dialog.
*/
public class BluetoothOppLauncherActivity extends Activity {
- private static final String TAG = "BluetoothLauncherActivity";
+ private static final String TAG = "BluetoothOppLauncherActivity";
private static final boolean D = Constants.DEBUG;
private static final boolean V = Constants.VERBOSE;
@@ -75,6 +75,11 @@
Intent intent = getIntent();
String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, " Received " + intent + " with null action");
+ finish();
+ return;
+ }
if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
//Check if Bluetooth is available in the beginning instead of at the end
@@ -88,6 +93,17 @@
return;
}
+ if (!BluetoothOppManager.isReadyForFileSharing) {
+ Log.i(TAG, " File share already in process, retrun with out any action ");
+ finish();
+ return;
+ }
+ /*
+ * SECURITY_EXCEPTION Google Photo grant-uri-permission
+ */
+ BTOppUtils.grantPermissionToUri(getApplicationContext(),
+ intent.getClipData());
+
/*
* Other application is trying to share a file via Bluetooth,
* probably Pictures, videos, or vCards. The Intent should contain
@@ -159,6 +175,7 @@
@Override
public void run() {
try {
+ BluetoothOppManager.isReadyForFileSharing = false;
BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
.saveSendingFileInfo(mimeType, uris, false /* isHandover */,
true /* fromExternal */);
@@ -167,7 +184,8 @@
launchDevicePicker();
finish();
} catch (IllegalArgumentException exception) {
- showToast(exception.getMessage());
+ Log.e(TAG, "SEND_MULTIPLE :" +exception.getMessage());
+ BluetoothOppManager.isReadyForFileSharing = true;
finish();
}
}
@@ -230,6 +248,7 @@
}
startActivity(in1);
}
+ BluetoothOppManager.isReadyForFileSharing = true;
}
/* Returns true if Bluetooth is allowed given current airplane mode settings. */
@@ -403,12 +422,14 @@
boolean fromExternal) {
BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
try {
+ BluetoothOppManager.isReadyForFileSharing = false;
manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal);
launchDevicePicker();
finish();
} catch (IllegalArgumentException exception) {
- showToast(exception.getMessage());
+ Log.e(TAG, "sendFileInfo :" + exception.getMessage());
finish();
+ BluetoothOppManager.isReadyForFileSharing = true;
}
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index 129d110..7d2d318 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -100,6 +100,8 @@
private static final int ALLOWED_INSERT_SHARE_THREAD_NUMBER = 3;
+ static boolean isReadyForFileSharing = true;
+
// used to judge if need continue sending process after received a
// ENABLED_ACTION
public boolean mSendingFlag;
@@ -194,6 +196,23 @@
return false;
}
+ synchronized void removeWhitelist(String address) {
+ Log.d(TAG, " removeWhitelist :" + address);
+ if (address == null) {
+ return;
+ }
+ // Remove any existing entries
+ for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+ Pair<String, Long> entry = iter.next();
+ if (entry.first.equals(address)) {
+ iter.remove();
+ Log.i(TAG," removeWhitelist device found removed ");
+ }
+ }
+ Log.d(TAG," removeWhitelist END :");
+ }
+
+
/**
* Restore data from preference
*/
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 41ffec3..ee7cadd 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -165,7 +165,7 @@
public void updateNotification() {
synchronized (BluetoothOppNotification.this) {
mPendingUpdate++;
- if (mPendingUpdate > 1) {
+ if ((mPendingUpdate > 1) && (mUpdateNotificationThread != null)) {
if (V) {
Log.v(TAG, "update too frequent, put in queue");
}
@@ -594,6 +594,7 @@
.setLocalOnly(true)
.build();
mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, n);
+ Log.i(TAG, " Incoming Notification ");
}
cursor.close();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index 1d01646..349951c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -490,7 +490,7 @@
if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
|| responseCode == ResponseCodes.OBEX_HTTP_OK) {
- if (V) {
+ if (D) {
Log.v(TAG, "Remote accept");
}
okToProceed = true;
@@ -503,7 +503,7 @@
Log.i(TAG, "Remote reject, Response code is " + responseCode);
}
}
-
+ long beginTime = System.currentTimeMillis();
while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) {
if (V) {
timestamp = SystemClock.elapsedRealtime();
@@ -556,6 +556,7 @@
Log.i(TAG,
"SendFile finished send out file " + fileInfo.mFileName + " length "
+ fileInfo.mLength);
+ BTOppUtils.throughputInKbps(fileInfo.mLength, beginTime);
} else {
error = true;
status = BluetoothShare.STATUS_CANCELED;
@@ -586,7 +587,7 @@
if (!error) {
responseCode = putOperation.getResponseCode();
if (responseCode != -1) {
- if (V) {
+ if (D) {
Log.v(TAG, "Get response code " + responseCode);
}
if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
@@ -643,7 +644,7 @@
super.interrupt();
synchronized (this) {
if (mWaitingForRemote) {
- if (V) {
+ if (D) {
Log.v(TAG, "Interrupted when waitingForRemote");
}
try {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index 47e80c8..dc07478 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -104,6 +104,10 @@
private int mNumFilesAttemptedToReceive;
+ private boolean isHandover = false;
+
+ private String destination;
+
public BluetoothOppObexServerSession(Context context, ObexTransport transport,
BluetoothOppService service) {
mContext = context;
@@ -112,6 +116,7 @@
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mPartialWakeLock.setReferenceCounted(false);
+ BTOppUtils.acquireFullWakeLock(pm, TAG);
}
@Override
@@ -129,6 +134,9 @@
Log.d(TAG, "Create ServerSession with transport " + mTransport.toString());
}
mSession = new ServerSession(mTransport, this, null);
+ if(BTOppUtils.isA2DPPlaying) {
+ mSession.reduceMTU(true);
+ }
} catch (IOException e) {
Log.e(TAG, "Create server session error" + e);
}
@@ -198,15 +206,13 @@
} else {
destination = "FF:FF:FF:00:00:00";
}
- boolean isWhitelisted =
- BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
HeaderSet request;
String name, mimeType;
Long length;
try {
request = op.getReceivedHeader();
- if (V) {
+ if (D) {
Constants.logHeader(request);
}
name = (String) request.getHeader(HeaderSet.NAME);
@@ -260,7 +266,7 @@
}
// Reject anything outside the "whitelist" plus unspecified MIME Types.
- if (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
+ if (mimeType == null || (!isHandover && !Constants.mimeTypeMatches(mimeType,
Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))) {
if (D) {
Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
@@ -275,29 +281,31 @@
values.put(BluetoothShare.DESTINATION, destination);
values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
values.put(BluetoothShare.TIMESTAMP, mTimestamp);
-
+ boolean needConfirm = true;
// It's not first put if !serverBlocking, so we auto accept it
if (!mServerBlocking && (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
|| mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED)) {
values.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
+ needConfirm = false;
}
- if (isWhitelisted) {
+ if (isHandover) {
values.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
+ needConfirm = false;
}
Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
-
- if (V) {
+ BTOppUtils.isTurnOnScreen(mContext ,needConfirm);
+ if (D) {
Log.v(TAG, "insert contentUri: " + contentUri);
Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
}
synchronized (this) {
- mPartialWakeLock.acquire();
+ BTOppUtils.acquirePartialWakeLock(mPartialWakeLock);
mServerBlocking = true;
try {
@@ -339,7 +347,7 @@
}
mAccepted = mInfo.mConfirm;
- if (V) {
+ if (D) {
Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
}
int status = BluetoothShare.STATUS_SUCCESS;
@@ -390,6 +398,8 @@
mInfo.mStatus = status;
msg.obj = mInfo;
msg.sendToTarget();
+ } else {
+ Log.w(TAG," Not sent MSG_SESSION_ERROR ");
}
}
} else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
@@ -429,6 +439,7 @@
/*
* implement receive file
*/
+ long beginTime = 0;
int status = -1;
BufferedOutputStream bos = null;
@@ -466,6 +477,7 @@
long currentTime;
long prevTimestamp = SystemClock.elapsedRealtime();
try {
+ beginTime = System.currentTimeMillis();
while ((!mInterrupted) && (position != fileInfo.mLength)) {
if (V) {
@@ -508,9 +520,15 @@
/* OBEX Abort packet received from remote device */
if ("Abort Received".equals(e1.getMessage())) {
status = BluetoothShare.STATUS_CANCELED;
+ Message msg = Message.obtain(mCallback,
+ BluetoothOppObexSession.MSG_SESSION_ERROR);
+ msg.obj = mInfo;
+ msg.sendToTarget();
+ mCallback = null;
} else {
status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
}
+ BTOppUtils.cleanFile(mFileInfo.mFileName);
error = true;
}
}
@@ -525,6 +543,7 @@
if (D) {
Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
}
+ BTOppUtils.throughputInKbps(fileInfo.mLength, beginTime);
status = BluetoothShare.STATUS_SUCCESS;
} else {
if (D) {
@@ -568,7 +587,7 @@
if (D) {
Log.d(TAG, "onConnect");
}
- if (V) {
+ if (D) {
Constants.logHeader(request);
}
Long objectCount = null;
@@ -586,13 +605,13 @@
Log.e(TAG, e.toString());
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
- String destination;
if (mTransport instanceof BluetoothObexTransport) {
destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
} else {
destination = "FF:FF:FF:00:00:00";
}
- boolean isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+ isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+ if (D) Log.d(TAG, "isHandover :" + isHandover);
if (isHandover) {
// Notify the handover requester file transfer has started
Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
@@ -623,7 +642,9 @@
}
private synchronized void releaseWakeLocks() {
+ BTOppUtils.releaseFullWakeLock();
if (mPartialWakeLock.isHeld()) {
+ if (D) Log.d(TAG, "releasing partial wakelock");
mPartialWakeLock.release();
}
}
@@ -631,7 +652,10 @@
@Override
public void onClose() {
if (D) {
- Log.d(TAG, "onClose");
+ Log.d(TAG, "onClose isHandover :" + isHandover);
+ }
+ if (isHandover) {
+ BluetoothOppManager.getInstance(mContext).removeWhitelist(destination);
}
releaseWakeLocks();
mBluetoothOppService.acceptNewConnections();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index 73d0194..edcbedf 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -58,10 +58,11 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
+ if(D) Log.d(TAG, "Action :" + action);
+ if (action == null) return;
if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
-
+ BluetoothOppManager.isReadyForFileSharing = false;
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (D) {
@@ -70,6 +71,7 @@
if (remoteDevice == null) {
mOppManager.cleanUpSendingFileInfo();
+ BluetoothOppManager.isReadyForFileSharing = true;
return;
}
// Insert transfer session record to database
@@ -85,6 +87,7 @@
} else {
toastMsg = context.getString(R.string.bt_toast_4, deviceName);
}
+ BluetoothOppManager.isReadyForFileSharing = true;
Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
} else if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) {
if (V) {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
index 6d7fe95..7130418 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
+import android.database.CursorWindowAllocationException;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.OpenableColumns;
@@ -119,9 +120,12 @@
} catch (SQLiteException e) {
// some content providers don't support the DISPLAY_NAME or SIZE columns
metadataCursor = null;
- } catch (SecurityException e) {
+ } catch (SecurityException | NullPointerException e) {
Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri);
return SEND_FILE_INFO_ERROR;
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, " generateFileInfo :" + e);
+ throw new IllegalArgumentException(e.toString());
}
if (metadataCursor != null) {
@@ -175,6 +179,10 @@
// As a second source of getting the correct file length,
// get a file descriptor and get the stat length
AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
+ if (fd == null) {
+ Log.e(TAG, "fd is NULL");
+ throw new IllegalArgumentException("Memory related error");
+ }
long statLength = fd.getLength();
if (length != statLength && statLength > 0) {
Log.e(TAG, "Content provider length is wrong (" + Long.toString(length)
@@ -206,6 +214,9 @@
}
} catch (FileNotFoundException e) {
// Ignore
+ } catch (SecurityException | IllegalStateException e) {
+ Log.e(TAG, "Error generateFileInfo ", e);
+ return SEND_FILE_INFO_ERROR;
}
}
@@ -224,6 +235,9 @@
return SEND_FILE_INFO_ERROR;
} catch (IOException e) {
return SEND_FILE_INFO_ERROR;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Error generateFileInfo ", e);
+ return SEND_FILE_INFO_ERROR;
}
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 383a497..f432635 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -115,6 +115,8 @@
private UpdateThread mUpdateThread;
+ private boolean mUpdateThreadRunning;
+
private ArrayList<BluetoothOppShareInfo> mShares;
private ArrayList<BluetoothOppBatch> mBatches;
@@ -191,7 +193,7 @@
@Override
protected void create() {
- if (V) {
+ if (D) {
Log.v(TAG, "onCreate");
}
mShares = Lists.newArrayList();
@@ -206,12 +208,13 @@
}.start();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ BTOppUtils.addA2dpFilter(filter);
registerReceiver(mBluetoothReceiver, filter);
mAdapter = BluetoothAdapter.getDefaultAdapter();
synchronized (BluetoothOppService.this) {
if (mAdapter == null) {
- Log.w(TAG, "Local BT device is not enabled");
+ Log.w(TAG, "Local BT is not enabled");
}
}
if (V) {
@@ -226,7 +229,7 @@
@Override
public boolean start() {
- if (V) {
+ if (D) {
Log.v(TAG, "start()");
}
mObserver = new BluetoothShareContentObserver();
@@ -313,6 +316,7 @@
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
+ Log.i(TAG, " handleMessage :" + msg.what);
switch (msg.what) {
case STOP_LISTENER:
stopListeners();
@@ -330,8 +334,19 @@
unregisterReceivers();
synchronized (BluetoothOppService.this) {
if (mUpdateThread != null) {
+ mUpdateThread.interrupt();
+ }
+ }
+ while (mUpdateThread != null && mUpdateThreadRunning) {
+ try {
+ Thread.sleep(50);
+ } catch (Exception e) {
+ Log.e(TAG, "Thread sleep", e);
+ }
+ }
+ synchronized (BluetoothOppService.this) {
+ if (mUpdateThread != null) {
try {
- mUpdateThread.interrupt();
mUpdateThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted", e);
@@ -339,7 +354,16 @@
mUpdateThread = null;
}
}
- mNotifier.cancelNotifications();
+ if (D) Log.d(TAG," clear batches");
+ if (mBatches != null) {
+ mBatches.clear();
+ }
+ if (mShares != null) {
+ mShares.clear();
+ }
+ if (mNotifier != null) {
+ mNotifier.cancelNotifications();
+ }
break;
case START_LISTENER:
if (mAdapter.isEnabled()) {
@@ -445,7 +469,8 @@
Log.d(TAG, "start Socket Listeners");
}
stopListeners();
- mServerSocket = ObexServerSockets.createInsecure(this);
+ mServerSocket = ObexServerSockets.createInsecureWithFixedChannels(this,
+ SdpManager.OPP_RFCOMM_CHANNEL, SdpManager.OPP_L2CAP_PSM);
acceptNewConnections();
SdpManager sdpManager = SdpManager.getDefaultManager();
if (sdpManager == null || mServerSocket == null) {
@@ -463,16 +488,10 @@
@Override
protected void cleanup() {
- if (V) {
+ if (D) {
Log.v(TAG, "onDestroy");
}
stopListeners();
- if (mBatches != null) {
- mBatches.clear();
- }
- if (mShares != null) {
- mShares.clear();
- }
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
@@ -484,9 +503,13 @@
getContentResolver().unregisterContentObserver(mObserver);
mObserver = null;
}
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "unregisterContentObserver " + e.toString());
+ }
+ try {
unregisterReceiver(mBluetoothReceiver);
} catch (IllegalArgumentException e) {
- Log.w(TAG, "unregisterReceivers " + e.toString());
+ Log.w(TAG, "unregisterReceiver " + e.toString());
}
}
@@ -504,7 +527,8 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
+ if (D) Log.d(TAG, "action : " + action);
+ if (action == null) return;
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
case BluetoothAdapter.STATE_ON:
@@ -541,6 +565,8 @@
mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
break;
}
+ } else {
+ BTOppUtils.checkAction(intent);
}
}
};
@@ -551,6 +577,7 @@
if (mUpdateThread == null) {
mUpdateThread = new UpdateThread();
mUpdateThread.start();
+ mUpdateThreadRunning = true;
}
}
}
@@ -580,6 +607,7 @@
while (!mIsInterrupted) {
synchronized (BluetoothOppService.this) {
if (mUpdateThread != this) {
+ mUpdateThreadRunning = false;
throw new IllegalStateException(
"multiple UpdateThreads in BluetoothOppService");
}
@@ -589,6 +617,7 @@
}
if (!mPendingUpdate) {
mUpdateThread = null;
+ mUpdateThreadRunning = false;
return;
}
mPendingUpdate = false;
@@ -598,6 +627,7 @@
BluetoothShare._ID);
if (cursor == null) {
+ mUpdateThreadRunning = false;
return;
}
@@ -646,6 +676,9 @@
if (V) {
Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
}
+ if (shouldScanFile(arrayPos)) {
+ scanFile(arrayPos);
+ }
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -668,6 +701,9 @@
// This cursor row already exists in the stored array.
updateShare(cursor, arrayPos);
+ if (shouldScanFile(arrayPos)) {
+ scanFile(arrayPos);
+ }
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -679,6 +715,9 @@
}
insertShare(cursor, arrayPos);
+ if (shouldScanFile(arrayPos)) {
+ scanFile(arrayPos);
+ }
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -691,6 +730,8 @@
cursor.close();
}
+
+ mUpdateThreadRunning = false;
}
}
@@ -1062,7 +1103,7 @@
if (V) {
Log.v(TAG, "Deleted shares, number = " + delNum);
}
-
+ BTOppUtils.cleanOnPowerOff(contentResolver);
// Keep the latest inbound and successful shares.
Cursor cursor =
contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 91af9ca..7bfc1ca 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -123,7 +123,7 @@
}
if ((device.equals(mBatch.mDestination)) && (mCurrentShare.mConfirm
== BluetoothShare.USER_CONFIRMATION_PENDING)) {
- if (V) {
+ if (D) {
Log.v(TAG, "ACTION_ACL_DISCONNECTED to be processed for batch: "
+ mBatch.mId);
}
@@ -203,6 +203,7 @@
@Override
public void handleMessage(Message msg) {
+ Log.i(TAG, " handleMessage :" + msg.what);
switch (msg.what) {
case SOCKET_ERROR_RETRY:
mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true);
@@ -217,7 +218,7 @@
if (V) {
Log.v(TAG, "receive TRANSPORT_ERROR msg");
}
- mConnectThread = null;
+
markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
@@ -230,7 +231,7 @@
if (V) {
Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
}
- mConnectThread = null;
+
mTransport = (ObexTransport) msg.obj;
startObexSession();
@@ -502,7 +503,7 @@
* Stop the transfer
*/
public void stop() {
- if (V) {
+ if (D) {
Log.v(TAG, "stop");
}
if (mSession != null) {
@@ -513,19 +514,22 @@
}
cleanUp();
- if (mConnectThread != null) {
- try {
- mConnectThread.interrupt();
- if (V) {
- Log.v(TAG, "waiting for connect thread to terminate");
+ synchronized (this) {
+ if (mConnectThread != null) {
+ try {
+ mConnectThread.interrupt();
+ if (D) {
+ Log.v(TAG, "waiting for connect thread to terminate");
+ }
+ mConnectThread.join();
+ } catch (InterruptedException e) {
+ if (V) {
+ Log.v(TAG, "Interrupted waiting for connect thread to join");
+ }
}
- mConnectThread.join();
- } catch (InterruptedException e) {
- if (V) {
- Log.v(TAG, "Interrupted waiting for connect thread to join");
- }
+ mConnectThread = null;
+ if (D) Log.d(TAG, "mConnectThread terminated");
}
- mConnectThread = null;
}
// Prevent concurrent access
synchronized (this) {
@@ -731,7 +735,7 @@
try {
mBtSocket.connect();
- if (V) {
+ if (D) {
Log.v(TAG,
"Rfcomm socket connection attempt took " + (System.currentTimeMillis()
- mTimestamp) + " ms");
@@ -799,7 +803,7 @@
}
try {
mBtSocket.connect();
- if (V) {
+ if (D) {
Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis()
- mTimestamp) + " ms");
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 1b5cd59..cb506d7 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -43,6 +43,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
+import android.database.SQLException;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
@@ -76,17 +77,22 @@
public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
- Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- fillRecord(context, cursor, info);
+ try {
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ fillRecord(context, cursor, info);
+ }
+ cursor.close();
+ } else {
+ info = null;
+ if (V) {
+ Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
+ }
}
- cursor.close();
- } else {
+ } catch (SQLException | NullPointerException e) {
info = null;
- if (V) {
- Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
- }
+ Log.e(TAG, "queryRecord Error: ", e);
}
return info;
}
@@ -204,8 +210,14 @@
return;
}
- Uri path = BluetoothOppFileProvider.getUriForFile(context,
- "com.android.bluetooth.opp.fileprovider", f);
+ Uri path = null;
+ try {
+ path = BluetoothOppFileProvider.getUriForFile(context,
+ "com.android.bluetooth.opp.fileprovider", f);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Not able to find root path:" + f.getAbsolutePath());
+ }
+
if (path == null) {
Log.w(TAG, "Cannot get content URI for the shared file");
return;
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index 3bf6cde..0dd612c 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -194,6 +194,9 @@
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/x-hwp",
+ "application/ogg",
+ "application/vnd.android.package-archive",
+ "text/comma-separated-values",
};
/** Where we store received files */
@@ -204,7 +207,7 @@
static final boolean DEBUG = true;
- static final boolean VERBOSE = false;
+ static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
static final int MAX_RECORDS_IN_DATABASE = 50;
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index 700b395..a7d682f 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -54,7 +54,8 @@
*/
public class PanService extends ProfileService {
private static final String TAG = "PanService";
- private static final boolean DBG = false;
+ private static final String LOG_TAG = "BluetoothPan";
+ private static final boolean DBG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private static PanService sPanService;
private static final String BLUETOOTH_IFACE_ADDR_START = "192.168.44.1";
@@ -323,6 +324,11 @@
Log.e(TAG, "Pan Device not disconnected: " + device);
return false;
}
+ /* Cancel discovery while initiating PANU connection, if It's in progress */
+ if (mAdapter != null && mAdapter.isDiscovering()) {
+ Log.d(TAG,"Inquiry is going on, Cancelling inquiry while initiating PANU connection");
+ mAdapter.cancelDiscovery();
+ }
Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
mHandler.sendMessage(msg);
return true;
@@ -501,6 +507,10 @@
+ ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
+ remoteRole);
}
+ if (device == null) {
+ Log.d(TAG, "BluetoothDevice is null, Ignoring state change ");
+ return;
+ }
int prevState;
BluetoothPanDevice panDevice = mPanDevices.get(device);
@@ -522,8 +532,9 @@
// connect call will put us in STATE_DISCONNECTED. Then, the disconnect completes and
// changes the state to STATE_DISCONNECTING. All future calls to BluetoothPan#connect
// will fail until the caller explicitly calls BluetoothPan#disconnect.
- if (prevState == BluetoothProfile.STATE_DISCONNECTED
- && state == BluetoothProfile.STATE_DISCONNECTING) {
+ if (prevState == BluetoothProfile.STATE_DISCONNECTED &&
+ (state == BluetoothProfile.STATE_DISCONNECTING ||
+ state == BluetoothProfile.STATE_DISCONNECTED)) {
Log.d(TAG, "Ignoring state change from " + prevState + " to " + state);
mPanDevices.remove(device);
return;
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index f709cd5..5468a76 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -109,7 +109,7 @@
Intent i = getIntent();
String action = i.getAction();
mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE);
- if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
+ if (action != null && action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
showPbapDialog(DIALOG_YES_NO_AUTH);
mCurrentDialog = DIALOG_YES_NO_AUTH;
} else {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 247a76d..e78fe20 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -58,7 +58,9 @@
import javax.obex.ResponseCodes;
import javax.obex.ServerRequestHandler;
-public class BluetoothPbapObexServer extends ServerRequestHandler {
+import com.android.bluetooth.pbap.BluetoothPbapSimVcardManager.SimPaths;
+
+public class BluetoothPbapObexServer extends ServerRequestHandler implements SimPaths{
private static final String TAG = "BluetoothPbapObexServer";
@@ -93,22 +95,12 @@
0x66
};
- // Currently not support SIM card
private static final String[] LEGAL_PATH = {
"/telecom",
"/telecom/pb",
"/telecom/ich",
"/telecom/och",
"/telecom/mch",
- "/telecom/cch"
- };
-
- @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = {
- "/telecom",
- "/telecom/pb",
- "/telecom/ich",
- "/telecom/och",
- "/telecom/mch",
"/telecom/cch",
"/SIM1",
"/SIM1/telecom",
@@ -202,6 +194,8 @@
private PbapStateMachine mStateMachine;
+ protected static BluetoothPbapSimVcardManager mVcardSimManager;
+
public static class ContentType {
public static final int PHONEBOOK = 1;
@@ -212,6 +206,8 @@
public static final int MISSED_CALL_HISTORY = 4;
public static final int COMBINED_CALL_HISTORY = 5;
+
+ public static final int SIM_PHONEBOOK = 6;
}
public BluetoothPbapObexServer(Handler callback, Context context,
@@ -221,6 +217,8 @@
mContext = context;
mVcardManager = new BluetoothPbapVcardManager(mContext);
mStateMachine = stateMachine;
+ mVcardSimManager = new BluetoothPbapSimVcardManager(mContext);
+ BluetoothPbapFixes.getFeatureSupport(mContext);
}
@Override
@@ -433,6 +431,12 @@
validName = false;
}
+ if (!BluetoothPbapFixes.isSimSupported && ((mCurrentPath.contains("SIM") ||
+ (validName && name.contains("SIM"))))) {
+ if (D) Log.d(TAG, "SIM support disabled ");
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ }
+
if (!validName || (validName && type.equals(TYPE_VCARD))) {
if (D) {
Log.d(TAG,
@@ -441,21 +445,23 @@
if (mCurrentPath.equals(PB_PATH)) {
appParamValue.needTag = ContentType.PHONEBOOK;
- } else if (mCurrentPath.equals(ICH_PATH)) {
+ } else if (mCurrentPath.equals(ICH_PATH)|| mCurrentPath.equals(SIM_ICH_PATH)) {
appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
- } else if (mCurrentPath.equals(OCH_PATH)) {
+ } else if (mCurrentPath.equals(OCH_PATH)|| mCurrentPath.equals(SIM_OCH_PATH)) {
appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
- } else if (mCurrentPath.equals(MCH_PATH)) {
+ } else if (mCurrentPath.equals(MCH_PATH)|| mCurrentPath.equals(SIM_MCH_PATH)) {
appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
mNeedNewMissedCallsNum = true;
- } else if (mCurrentPath.equals(CCH_PATH)) {
+ } else if (mCurrentPath.equals(CCH_PATH)|| mCurrentPath.equals(SIM_CCH_PATH)) {
appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
- } else if (mCurrentPath.equals(TELECOM_PATH)) {
+ } else if (mCurrentPath.equals(TELECOM_PATH)|| mCurrentPath.equals(SIM_PATH)) {
/* PBAP 1.1.1 change */
if (!validName && type.equals(TYPE_LISTING)) {
Log.e(TAG, "invalid vcard listing request in default folder");
return ResponseCodes.OBEX_HTTP_NOT_FOUND;
}
+ } else if (mCurrentPath.equals(SIM_PB_PATH)) {
+ appParamValue.needTag = ContentType.SIM_PHONEBOOK;
} else {
Log.w(TAG, "mCurrentpath is not valid path!!!");
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -464,16 +470,14 @@
Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
}
} else {
- // Not support SIM card currently
- if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
- Log.w(TAG, "Not support access SIM card info!");
- return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
- }
-
// we have weak name checking here to provide better
// compatibility with other devices,although unique name such as
// "pb.vcf" is required by SIG spec.
- if (isNameMatchTarget(name, PB)) {
+ if (mVcardSimManager.isSimPhoneBook(name, type, PB, SIM1,
+ TYPE_PB, TYPE_LISTING, mCurrentPath)) {
+ appParamValue.needTag = ContentType.SIM_PHONEBOOK;
+ if (D) Log.d(TAG, "download SIM phonebook request");
+ } else if (isNameMatchTarget(name, PB)) {
appParamValue.needTag = ContentType.PHONEBOOK;
if (D) {
Log.v(TAG, "download phonebook request");
@@ -570,7 +574,7 @@
return false;
}
- private class AppParamValue {
+ class AppParamValue {
public int maxListCount;
public int listStartOffset;
@@ -746,25 +750,36 @@
int size) {
StringBuilder result = new StringBuilder();
int itemsFound = 0;
+ String type = "";
result.append("<?xml version=\"1.0\"?>");
result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
result.append("<vCard-listing version=\"1.0\">");
// Phonebook listing request
if (appParamValue.needTag == ContentType.PHONEBOOK) {
- String type = "";
if (appParamValue.searchAttr.equals("0")) {
type = "name";
} else if (appParamValue.searchAttr.equals("1")) {
type = "number";
}
if (type.length() > 0) {
- itemsFound = createList(appParamValue, needSendBody, size, result, type);
+ itemsFound = BluetoothPbapFixes.createList(mVcardSimManager, mVcardManager,
+ this, mVcardSelector, mOrderBy, appParamValue, needSendBody, size, result,
+ type);
} else {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
+ // SIM Phonebook listing Request
+ } else if (appParamValue.needTag == ContentType.SIM_PHONEBOOK) {
+ type = mVcardSimManager.getType(appParamValue.searchAttr);
+ if (type.length() > 0) {
+ itemsFound = BluetoothPbapFixes.createList(mVcardSimManager, mVcardManager,
+ this,mVcardSelector,mOrderBy,appParamValue, needSendBody, size, result, type);
+ } else {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ // Call history listing request
} else {
- // Call history listing request
ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
int requestSize =
nameList.size() >= appParamValue.maxListCount ? appParamValue.maxListCount
@@ -896,7 +911,7 @@
}
/** Function to send vcard data to client */
- private int pushBytes(Operation op, final String vcardString) {
+ protected int pushBytes(Operation op, final String vcardString) {
if (vcardString == null) {
Log.w(TAG, "vcardString is null!");
return ResponseCodes.OBEX_HTTP_OK;
@@ -971,6 +986,8 @@
nmnum = nmnum > 0 ? nmnum : 0;
misnum[0] = (byte) nmnum;
+ ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
+ ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
if (D) {
Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= "
+ nmnum);
@@ -980,7 +997,7 @@
if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
setDbCounters(ap);
}
- if (needSendPhonebookVersionCounters) {
+ if (BluetoothPbapFixes.isSupportedPbap12 && needSendPhonebookVersionCounters) {
setFolderVersionCounters(ap);
}
if (needSendCallHistoryVersionCounters) {
@@ -1042,7 +1059,8 @@
}
}
- if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
+ if (BluetoothPbapFixes.isSupportedPbap12
+ && checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
setDbCounters(ap);
reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
try {
@@ -1053,7 +1071,7 @@
}
}
- if (needSendPhonebookVersionCounters) {
+ if (BluetoothPbapFixes.isSupportedPbap12 && needSendPhonebookVersionCounters) {
setFolderVersionCounters(ap);
reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
try {
@@ -1158,6 +1176,7 @@
if (strIndex.trim().length() != 0) {
try {
intIndex = Integer.parseInt(strIndex);
+ if (D) Log.d(TAG, "Index: " + intIndex + "orderby: " + mOrderBy);
} catch (NumberFormatException e) {
Log.e(TAG, "catch number format exception " + e.toString());
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -1178,7 +1197,7 @@
Log.w(TAG, "wrong path!");
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
} else if (appParamValue.needTag == ContentType.PHONEBOOK) {
- if (intIndex < 0 || intIndex >= size) {
+ if (intIndex < 0 || !BluetoothPbapFixes.checkContactsVcardId(intIndex, mContext)) {
Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
return ResponseCodes.OBEX_HTTP_NOT_FOUND;
} else if (intIndex == 0) {
@@ -1190,6 +1209,10 @@
return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
}
+ } else if (appParamValue.needTag == ContentType.SIM_PHONEBOOK) {
+ return mVcardSimManager.initiatePullSimVcardEntry(intIndex, size,
+ vcard21, mOrderBy, name, op, mVcardManager, appParamValue.ignorefilter,
+ appParamValue.propertySelector, this);
} else {
if (intIndex <= 0 || intIndex > size) {
Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
@@ -1244,7 +1267,8 @@
}
// Limit the number of call log to CALLLOG_NUM_LIMIT
- if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+ if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK
+ && (appParamValue.needTag != BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK)) {
if (requestSize > CALLLOG_NUM_LIMIT) {
requestSize = CALLLOG_NUM_LIMIT;
}
@@ -1278,6 +1302,10 @@
appParamValue.propertySelector, appParamValue.vCardSelector,
appParamValue.vCardSelectorOperator, mVcardSelector);
}
+ } else if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK) {
+ return mVcardSimManager.initiatePullSimPhonebook(startPoint,
+ endPoint, vcard21, op, mVcardManager, appParamValue.ignorefilter,
+ appParamValue.propertySelector, this);
} else {
return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
@@ -1366,7 +1394,7 @@
}
}
- private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
+ protected void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
result.append("<card handle=\"");
result.append(vcfIndex);
result.append(".vcf\" name=\"");
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 374b5a1..66ef200 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -36,6 +36,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothPbap;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -47,6 +48,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
@@ -68,17 +70,18 @@
public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
private static final String TAG = "BluetoothPbapService";
+ private static final String LOG_TAG = "BluetoothPbap";
/**
* To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
* restart com.android.bluetooth process. only enable DEBUG log:
* "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
- * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
+ * DEBUG log: "setprop log.tag.BluetoothPbap VERBOSE"
*/
public static final boolean DEBUG = true;
- public static final boolean VERBOSE = false;
+ public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
/**
* Intent indicating incoming obex authentication request which is from
@@ -127,9 +130,12 @@
static final int CONTACTS_LOADED = 5;
static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
static final int ROLLOVER_COUNTERS = 7;
+ static final int GET_LOCAL_TELEPHONY_DETAILS = 8;
+ static final int CLEANUP_HANDLER_TASKS = 9;
static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
static final int RELEASE_WAKE_LOCK_DELAY = 10000;
+ static final int CLEANUP_HANDLER_DELAY = 50;
private PowerManager.WakeLock mWakeLock;
@@ -147,7 +153,7 @@
private static final int PBAP_NOTIFICATION_ID_START = 1000000;
private static final int PBAP_NOTIFICATION_ID_END = 2000000;
- private int mSdpHandle = -1;
+ protected int mSdpHandle = -1;
protected Context mContext;
@@ -219,7 +225,7 @@
if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
if (savePreference) {
device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)");
}
}
@@ -227,7 +233,7 @@
} else {
if (savePreference) {
device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)");
}
}
@@ -254,6 +260,43 @@
}
sm.sendMessage(PbapStateMachine.AUTH_CANCELLED);
}
+ } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (bondState == BluetoothDevice.BOND_BONDED && BluetoothPbapFixes.isSupportedPbap12) {
+ if (VERBOSE) Log.v(TAG, "Remote Device Type: " + remoteDevice.getType());
+ if (remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC ||
+ remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_DUAL)
+ remoteDevice.sdpSearch(BluetoothUuid.PBAP_PCE);
+ }
+ } else if (BluetoothDevice.ACTION_SDP_RECORD.equals(action)) {
+ ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
+ if (BluetoothUuid.PBAP_PCE.equals(uuid)) {
+ Log.d(TAG, "Received SDP Response for PCE UUID: " + uuid.toString());
+ BluetoothDevice remoteDevice = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE);
+ boolean hasPbap12Support =
+ BluetoothPbapFixes.remoteSupportsPbap1_2(remoteDevice);
+ String isRebonded = BluetoothPbapFixes.PbapSdpResponse.get(
+ remoteDevice.getAddress().substring(0,8));
+ Integer remoteVersion = BluetoothPbapFixes.remoteVersion.get(
+ remoteDevice.getAddress().substring(0,8));
+ if (hasPbap12Support && (isRebonded.equals("N"))) {
+ Log.d(TAG, "Remote Supports PBAP 1.2. Notify user");
+ BluetoothPbapFixes.createNotification(this, true);
+ } else if (!hasPbap12Support
+ && (remoteVersion != null &&
+ remoteVersion.intValue() < BluetoothPbapFixes.PBAP_ADV_VERSION)
+ && (isRebonded != null && isRebonded.equals("N"))) {
+ Log.d(TAG, "Remote PBAP profile support downgraded");
+ BluetoothPbapFixes.createNotification(this, false);
+ } else {
+ Log.d(TAG, "Notification Not Required.");
+ if (BluetoothPbapFixes.mNotificationManager != null)
+ BluetoothPbapFixes.mNotificationManager.cancelAll();
+ }
+ }
} else {
Log.w(TAG, "Unhandled intent action: " + action);
}
@@ -283,6 +326,16 @@
if (mSessionStatusHandler != null) {
mSessionStatusHandler.removeCallbacksAndMessages(null);
}
+
+ mSessionStatusHandler.sendMessageDelayed(
+ mSessionStatusHandler.obtainMessage(CLEANUP_HANDLER_TASKS), CLEANUP_HANDLER_DELAY);
+ }
+
+ private void cleanUpHandlerTasks() {
+ Log.d(TAG, "quitHandlerThread");
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
}
private void cleanUpServerSocket() {
@@ -305,11 +358,9 @@
if (mSdpHandle > -1) {
Log.w(TAG, "createSdpRecord, SDP record already created");
}
- mSdpHandle = SdpManager.getDefaultManager()
- .createPbapPseRecord("OBEX Phonebook Access Server",
- mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(),
- SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES,
- SDP_PBAP_SUPPORTED_FEATURES);
+ BluetoothPbapFixes.getFeatureSupport(mContext);
+ BluetoothPbapFixes.createSdpRecord(mServerSockets, this);
+
if (DEBUG) {
Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle);
}
@@ -346,7 +397,9 @@
switch (msg.what) {
case START_LISTENER:
- mServerSockets = ObexServerSockets.create(BluetoothPbapService.this);
+ mServerSockets = ObexServerSockets.createWithFixedChannels
+ (sBluetoothPbapService, SdpManager.PBAP_RFCOMM_CHANNEL,
+ SdpManager.PBAP_L2CAP_PSM);
if (mServerSockets == null) {
Log.w(TAG, "ObexServerSockets.create() returned null");
break;
@@ -408,6 +461,12 @@
mPbapStateMachineMap.remove(remoteDevice);
}
break;
+ case GET_LOCAL_TELEPHONY_DETAILS:
+ getLocalTelephonyDetails();
+ break;
+ case CLEANUP_HANDLER_TASKS:
+ cleanUpHandlerTasks();
+ break;
default:
break;
}
@@ -494,6 +553,8 @@
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(AUTH_RESPONSE_ACTION);
filter.addAction(AUTH_CANCELLED_ACTION);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
BluetoothPbapConfig.init(this);
registerReceiver(mPbapReceiver, filter);
try {
@@ -505,20 +566,15 @@
Log.e(TAG, "SQLite exception: " + e);
} catch (IllegalStateException e) {
Log.e(TAG, "Illegal state exception, content observer is already registered");
+ } catch (SecurityException e) {
+ Log.e(TAG, "Error while rigistering ContactChangeObserver " + e);
}
- TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- if (tm != null) {
- sLocalPhoneNum = tm.getLine1Number();
- sLocalPhoneName = tm.getLine1AlphaTag();
- if (TextUtils.isEmpty(sLocalPhoneName)) {
- sLocalPhoneName = this.getString(R.string.localPhoneName);
- }
- }
-
+ setBluetoothPbapService(this);
+ mSessionStatusHandler.sendMessage(
+ mSessionStatusHandler.obtainMessage(GET_LOCAL_TELEPHONY_DETAILS));
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
- setBluetoothPbapService(this);
return true;
}
@@ -531,9 +587,6 @@
if (mSessionStatusHandler != null) {
mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
}
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
mContactsLoaded = false;
if (mContactChangeObserver == null) {
Log.i(TAG, "Avoid unregister when receiver it is not registered");
@@ -667,7 +720,10 @@
+ " socket=" + socket);
return false;
}
-
+ if (getConnectedDevices().size() >= BluetoothPbapFixes.MAX_CONNECTED_DEVICES) {
+ Log.i(TAG, "Cannot connect to " + remoteDevice + " multiple devices connected already");
+ return false;
+ }
PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice,
socket, this, mSessionStatusHandler, mNextNotificationId);
mNextNotificationId++;
@@ -751,9 +807,14 @@
Runnable r = new Runnable() {
@Override
public void run() {
- BluetoothPbapUtils.loadAllContacts(mContext,
- mSessionStatusHandler);
- mThreadLoadContacts = null;
+ try {
+ BluetoothPbapUtils.loadAllContacts(mContext,
+ mSessionStatusHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "loadAllContacts failed: " + e);
+ } finally {
+ mThreadLoadContacts = null;
+ }
}
};
mThreadLoadContacts = new Thread(r);
@@ -766,13 +827,32 @@
Runnable r = new Runnable() {
@Override
public void run() {
- BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
- mSessionStatusHandler);
- mThreadUpdateSecVersionCounter = null;
+ try {
+ BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
+ mSessionStatusHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "updateSecondaryVersion counter failed: " + e);
+ } finally {
+ mThreadUpdateSecVersionCounter = null;
+ }
}
};
mThreadUpdateSecVersionCounter = new Thread(r);
mThreadUpdateSecVersionCounter.start();
}
}
+
+ private void getLocalTelephonyDetails() {
+ TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ sLocalPhoneNum = tm.getLine1Number();
+ sLocalPhoneName = tm.getLine1AlphaTag();
+ if (TextUtils.isEmpty(sLocalPhoneName)) {
+ sLocalPhoneName = this.getString(R.string.localPhoneName);
+ }
+ }
+ if (VERBOSE)
+ Log.v(TAG, "Local Phone Details- Number:" + sLocalPhoneNum
+ + ", Name:" + sLocalPhoneName);
+ }
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index f8b8e5f..a5c3ec3 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -21,6 +21,7 @@
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
@@ -199,7 +200,8 @@
}
}
- static void loadAllContacts(Context context, Handler handler) {
+ static void loadAllContacts(Context context, Handler handler)
+ throws IllegalStateException, SQLiteException {
if (V) {
Log.v(TAG, "Loading Contacts ...");
}
@@ -213,7 +215,8 @@
handler.sendMessage(handler.obtainMessage(BluetoothPbapService.CONTACTS_LOADED));
}
- static void updateSecondaryVersionCounter(Context context, Handler handler) {
+ static void updateSecondaryVersionCounter(Context context, Handler handler)
+ throws IllegalStateException, SQLiteException {
/* updatedList stores list of contacts which are added/updated after
* the time when contacts were last updated. (contactsLastUpdated
* indicates the time when contact/contacts were last updated and
@@ -229,9 +232,15 @@
Log.d(TAG, "Failed to fetch data from contact database");
return;
}
+ int indexCid = c.getColumnIndex(Contacts._ID);
+ int indexTimestamp = c.getColumnIndex(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
while (c.moveToNext()) {
- String contactId = c.getString(0);
- long lastUpdatedTime = c.getLong(1);
+ if (c.isNull(indexCid)) {
+ Log.d(TAG, "Skipping unavailable contact from cursor.");
+ continue;
+ }
+ String contactId = c.getString(indexCid);
+ long lastUpdatedTime = c.getLong(indexTimestamp);
if (lastUpdatedTime > sContactsLastUpdated) {
updatedList.add(contactId);
}
@@ -430,6 +439,10 @@
int indexMimeType = c.getColumnIndex(Data.MIMETYPE);
String contactId, data, mimeType;
while (c.moveToNext()) {
+ if (c.isNull(indexCId) || c.isNull(indexMimeType)) {
+ Log.e(TAG, "Contact data in cursor is not found. Skipping.");
+ continue;
+ }
contactId = c.getString(indexCId);
data = c.getString(indexData);
mimeType = c.getString(indexMimeType);
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index e58fa2e..c118b18 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -83,6 +83,7 @@
static final String[] PHONES_CONTACTS_PROJECTION = new String[]{
Phone.CONTACT_ID, // 0
Phone.DISPLAY_NAME, // 1
+ Phone.ACCOUNT_TYPE_AND_DATA_SET, //2
};
static final String[] PHONE_LOOKUP_PROJECTION = new String[]{
@@ -102,6 +103,7 @@
static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
private static final int NEED_SEND_BODY = -1;
+ protected static boolean isPullVcardEntry = false;
public BluetoothPbapVcardManager(final Context context) {
mContext = context;
@@ -155,6 +157,9 @@
case BluetoothPbapObexServer.ContentType.PHONEBOOK:
size = getContactsSize();
break;
+ case BluetoothPbapObexServer.ContentType.SIM_PHONEBOOK:
+ size = BluetoothPbapObexServer.mVcardSimManager.getSIMContactsSize();
+ break;
default:
size = getCallHistorySize(type);
break;
@@ -168,19 +173,27 @@
public final int getContactsSize() {
final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
Cursor contactCursor = null;
+ MatrixCursor mCursor = null;
try {
- contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null,
- Phone.CONTACT_ID);
+ contactCursor = mResolver.query(
+ myUri,
+ new String[] {Phone.CONTACT_ID, Phone.ACCOUNT_TYPE_AND_DATA_SET},
+ null, null, Phone.CONTACT_ID);
if (contactCursor == null) {
return 0;
}
- return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf
+ mCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
+ return mCursor.getCount() + 1; // always has the 0.vcf
} catch (CursorWindowAllocationException e) {
Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
} finally {
if (contactCursor != null) {
contactCursor.close();
}
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
}
return 0;
}
@@ -260,7 +273,7 @@
if (ownerName == null || ownerName.length() == 0) {
ownerName = BluetoothPbapService.getLocalPhoneName();
}
- nameList.add(ownerName);
+ nameList.add(ownerName + "," + "0");
//End enhancement
final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
@@ -323,7 +336,7 @@
if (ownerName == null || ownerName.length() == 0) {
ownerName = BluetoothPbapService.getLocalPhoneName();
}
- nameList.add(ownerName);
+ nameList.add(ownerName + "," + "0");
// End enhancement
final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
@@ -332,12 +345,17 @@
contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
Phone.CONTACT_ID);
+ ArrayList<String> contactNameIdList = new ArrayList<String>();
+ contactCursor = getContactNameIdList(contactCursor,
+ contactNameIdList, mContext.getString(android.R.string.unknownName));
if (contactCursor != null) {
if (!composer.initWithCallback(contactCursor,
new EnterpriseRawContactEntitlesInfoCallback())) {
return nameList;
}
+ int i = 0;
+ contactCursor.moveToFirst();
while (!composer.isAfterLast()) {
String vcard = composer.createOneEntry();
if (vcard == null) {
@@ -362,8 +380,9 @@
if (TextUtils.isEmpty(name)) {
name = mContext.getString(android.R.string.unknownName);
}
- nameList.add(name);
+ nameList.add(contactNameIdList.get(i));
}
+ i++;
}
if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
if (V) {
@@ -558,6 +577,7 @@
try {
contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
Phone.CONTACT_ID);
+ contactCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
if (contactCursor != null) {
contactIdCursor =
ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint);
@@ -570,7 +590,7 @@
}
}
- if (vcardselect) {
+ if (BluetoothPbapFixes.isSupportedPbap12 && vcardselect) {
return composeContactsAndSendSelectedVCards(op, contactIdCursor, vcardType21,
ownerVCard, needSendBody, pbSize, ignorefilter, filter, vcardselector,
vcardselectorop);
@@ -603,6 +623,7 @@
} catch (CursorWindowAllocationException e) {
Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard");
} finally {
+ contactCursor = BluetoothPbapFixes.filterOutSimContacts(contactCursor);
if (contactCursor != null) {
contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset);
contactCursor.close();
@@ -624,6 +645,7 @@
* @return a cursor containing contact id of {@code offset} contact.
*/
public static Cursor filterByOffset(Cursor contactCursor, int offset) {
+ isPullVcardEntry = true;
return filterByRange(contactCursor, offset, offset);
}
@@ -644,6 +666,10 @@
final MatrixCursor contactIdsCursor = new MatrixCursor(new String[]{
Phone.CONTACT_ID
});
+ if (startPoint == endPoint && isPullVcardEntry) {
+ return BluetoothPbapFixes.getVcardEntry(contactCursor,
+ contactIdsCursor, contactIdColumn, startPoint);
+ }
while (contactCursor.moveToNext() && currentOffset <= endPoint) {
long currentContactId = contactCursor.getLong(contactIdColumn);
if (previousContactId != currentContactId) {
@@ -1315,17 +1341,21 @@
final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
final int idColumn = cursor.getColumnIndex(Data._ID);
final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
+ final int accountIndex = cursor.getColumnIndex(Phone.ACCOUNT_TYPE_AND_DATA_SET);
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
final long contactId =
cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
String displayName = nameColumn != -1 ? cursor.getString(nameColumn) : defaultName;
+ String accountType = accountIndex != -1 ? cursor.getString(accountIndex) :
+ BluetoothPbapFixes.getAccount(contactId);
if (TextUtils.isEmpty(displayName)) {
displayName = defaultName;
}
String newString = displayName + "," + contactId;
- if (!resultList.contains(newString)) {
+ if (!resultList.contains(newString) &&
+ !(accountType != null && accountType.startsWith("com.android.sim"))) {
resultList.add(newString);
}
}
@@ -1335,4 +1365,36 @@
}
}
}
+
+ /* creates name and id list of Non-sim contacts as display_name + "," + contact_id */
+ protected static Cursor getContactNameIdList(Cursor cursor,
+ ArrayList<String> contactIdList, String unknownName) {
+ if (cursor == null)
+ return null;
+ MatrixCursor mCursor = new MatrixCursor(new String[]{
+ Phone.CONTACT_ID
+ });
+
+ long previousContactId = -1;
+ final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
+ final int idColumn = cursor.getColumnIndex(Data._ID);
+ final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
+ final int account_col_id = cursor.getColumnIndex(Phone.ACCOUNT_TYPE_AND_DATA_SET);
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ long currentContactId = contactIdColumn != -1 ? cursor.getLong(contactIdColumn)
+ : cursor.getLong(idColumn);
+ String displayName = nameColumn != -1 ? cursor.getString(nameColumn)
+ : unknownName;
+ String accType = cursor.getString(account_col_id);
+ if (previousContactId != currentContactId &&
+ !(accType != null && accType.startsWith("com.android.sim"))) {
+ if (V) Log.v(TAG, displayName + "," + currentContactId);
+ previousContactId = currentContactId;
+ mCursor.addRow(new Long[]{currentContactId});
+ contactIdList.add(displayName + "," + Long.toString(currentContactId));
+ }
+ }
+ return mCursor;
+ }
}
diff --git a/src/com/android/bluetooth/pbap/PbapStateMachine.java b/src/com/android/bluetooth/pbap/PbapStateMachine.java
index 0f53be5..c850d8c 100644
--- a/src/com/android/bluetooth/pbap/PbapStateMachine.java
+++ b/src/com/android/bluetooth/pbap/PbapStateMachine.java
@@ -342,6 +342,8 @@
}
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
+ BluetoothPbapFixes.updateMtu(mServerSession, transport.isSrmSupported(),
+ mConnSocket.getMaxReceivePacketSize());
// It's ok to just use one wake lock
// Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
}
@@ -392,9 +394,11 @@
.setFlag(Notification.FLAG_AUTO_CANCEL, true)
.setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
.setContentIntent(
- PendingIntent.getActivity(mService, 0, clickIntent, 0))
+ PendingIntent.getActivity(mService, 0, clickIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
.setDeleteIntent(
- PendingIntent.getBroadcast(mService, 0, deleteIntent, 0))
+ PendingIntent.getBroadcast(mService, 0, deleteIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
.setLocalOnly(true)
.build();
nm.notify(mNotificationId, notification);
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 66e0b19..fea5002 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -42,9 +42,10 @@
private static final String SDP_SAP_SERVICE_NAME = "SIM Access";
private static final int SDP_SAP_VERSION = 0x0102;
+ private static final String LOG_TAG = "BluetoothSap";
private static final String TAG = "SapService";
- public static final boolean DEBUG = false;
- public static final boolean VERBOSE = false;
+ public static final boolean DEBUG = true;
+ public static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
/* Message ID's */
private static final int START_LISTENER = 1;
@@ -155,9 +156,9 @@
// It is mandatory for MSE to support initiation of bonding and encryption.
// TODO: Consider reusing the mServerSocket - it is indented to be reused
// for multiple connections.
- mServerSocket = mAdapter.listenUsingRfcommOn(
- BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
removeSdpRecord();
+ mServerSocket = mAdapter.listenUsingRfcommOn(
+ SdpManager.SAP_RFCOMM_CHANNEL, true, true);
mSdpHandle = SdpManager.getDefaultManager()
.createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(),
SDP_SAP_VERSION);
@@ -367,7 +368,7 @@
}
int permission = mRemoteDevice.getSimAccessPermission();
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "getSimAccessPermission() = " + permission);
}
@@ -394,7 +395,7 @@
setUserTimeoutAlarm();
sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "waiting for authorization for connection from: "
+ sRemoteDeviceName);
}
@@ -433,9 +434,7 @@
switch (msg.what) {
case START_LISTENER:
- if (mAdapter.isEnabled()) {
- startRfcommSocketListener();
- }
+ startRfcommSocketListener();
break;
case USER_TIMEOUT:
if (mIsWaitingAuthorization) {
@@ -621,7 +620,6 @@
Log.v(TAG, "start()");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
@@ -763,25 +761,6 @@
Log.v(TAG, "onReceive");
}
String action = intent.getAction();
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- int state =
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- if (state == BluetoothAdapter.STATE_TURNING_OFF) {
- if (DEBUG) {
- Log.d(TAG, "STATE_TURNING_OFF");
- }
- sendShutdownMessage();
- } else if (state == BluetoothAdapter.STATE_ON) {
- if (DEBUG) {
- Log.d(TAG, "STATE_ON");
- }
- // start RFCOMM listener
- mSessionStatusHandler.sendMessage(
- mSessionStatusHandler.obtainMessage(START_LISTENER));
- }
- return;
- }
-
if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
if (!mIsWaitingAuthorization) {
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index d0cca25..340fac0 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -49,6 +49,21 @@
public static final byte PBAP_REPO_SPEED_DAIL = 0x01 << 2;
public static final byte PBAP_REPO_FAVORITES = 0x01 << 3;
+ public static final int OPP_L2CAP_PSM = 0x1023;
+ public static final int PBAP_L2CAP_PSM = 0x1025;
+ public static final int MNS_L2CAP_PSM = 0x1027;
+ public static final int MAP_L2CAP_PSM = 0x1029;
+
+ public static final int OPP_RFCOMM_CHANNEL = 12;
+ public static final int SAP_RFCOMM_CHANNEL = 16;
+ public static final int PBAP_RFCOMM_CHANNEL = 19;
+ public static final int MNS_RFCOMM_CHANNEL = 22;
+ /* SMS/MMS will use channel 26 & Email channels are set from 27 to 30*/
+ public static final int MAP_RFCOMM_CHANNEL = 26;
+
+ public static final int NEXT_RFCOMM_CHANNEL = 1;
+ public static final int NEXT_L2CAP_CHANNEL = 2;
+
/* Variables to keep track of ongoing and queued search requests.
* mTrackerLock must be held, when using/changing sSdpSearchTracker
* and mSearchInProgress. */