DO NOT MERGE ANYWHERE Add missing null pointer check inside HeadSetClientService.stop() am: 3fe282a78e -s ours
am: 8080442c99 -s ours
* commit '8080442c99578192d91eb8fcc23ac9cc0c805587':
DO NOT MERGE ANYWHERE Add missing null pointer check inside HeadSetClientService.stop()
Change-Id: Id550b4448b5f3eab200c519e347e876579a4cc4c
diff --git a/Android.mk b/Android.mk
index 7b724d0..65f08b6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,14 +15,16 @@
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
+ $(call all-java-files-under, src) \
+ $(call all-proto-files-under, src)
LOCAL_PACKAGE_NAME := Bluetooth
LOCAL_CERTIFICATE := platform
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common libprotobuf-java-micro
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static android-support-v4
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common libprotobuf-java-micro services.net
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static android-support-v4 services.net
+LOCAL_PROTOC_OPTIMIZE_TYPE := micro
LOCAL_REQUIRED_MODULES := bluetooth.default
LOCAL_MULTILIB := 32
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3652fd3..4c739eb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,13 +25,14 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BLUETOOTH_MAP" />
+ <uses-permission android:name="android.permission.DUMP" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
- <!-- WRITE_CONTACTS is used for test cases only -->
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.NFC_HANDOVER_STATUS" />
@@ -44,11 +45,13 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="com.google.android.gallery3d.permission.GALLERY_PROVIDER"/>
<uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
@@ -62,6 +65,8 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<!-- For PBAP Owner Vcard Info -->
<uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -71,7 +76,9 @@
android:persistent="false"
android:label="@string/app_name"
android:supportsRtl="true"
- android:usesCleartextTraffic="false">
+ android:usesCleartextTraffic="false"
+ android:directBootAware="true"
+ android:defaultToDeviceProtectedStorage="true">
<uses-library android:name="javax.obex" />
<provider android:name=".opp.BluetoothOppProvider"
android:authorities="com.android.bluetooth.opp"
@@ -292,7 +299,7 @@
</service>
<service
android:process="@string/process"
- android:name = ".a2dp.A2dpSinkService"
+ android:name = ".a2dpsink.A2dpSinkService"
android:enabled="@bool/profile_supported_a2dp_sink">
<intent-filter>
<action android:name="android.bluetooth.IBluetoothA2dpSink" />
@@ -300,6 +307,15 @@
</service>
<service
android:process="@string/process"
+ android:name=".a2dpsink.mbs.A2dpMediaBrowserService"
+ android:exported="true"
+ android:enabled="@bool/profile_supported_a2dp_sink">
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
android:name = ".avrcp.AvrcpControllerService"
android:enabled="@bool/profile_supported_avrcp_controller">
<intent-filter>
@@ -330,7 +346,7 @@
<action android:name="android.bluetooth.IBluetoothPan" />
</intent-filter>
</service>
- <service
+ <service
android:process="@string/process"
android:name = ".hfpclient.HeadsetClientService"
android:enabled="@bool/profile_supported_hfpclient">
@@ -338,5 +354,36 @@
<action android:name="android.bluetooth.IBluetoothHeadsetClient" />
</intent-filter>
</service>
+ <service
+ android:process="@string/process"
+ android:name=".hfpclient.connserv.HfpClientConnectionService"
+ android:permission="android.permission.BIND_CONNECTION_SERVICE"
+ android:enabled="@bool/profile_supported_hfpclient">
+ <intent-filter>
+ <!-- Mechanism for Telecom stack to connect -->
+ <action android:name="android.telecom.ConnectionService" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
+ android:name = ".pbapclient.PbapClientService"
+ android:enabled="@bool/profile_supported_pbapclient">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothPbapClient" />
+ </intent-filter>
+ </service>
+ <!-- Authenticator for PBAP account. -->
+ <service
+ android:process="@string/process"
+ android:name=".AuthenticationService"
+ android:exported="true"
+ android:enabled="@bool/profile_supported_pbapclient">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+ <meta-data
+ android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator" />
+ </service>
</application>
</manifest>
diff --git a/AndroidManifest_test.xml b/AndroidManifest_test.xml
index ae21b88..8529ba5 100644
--- a/AndroidManifest_test.xml
+++ b/AndroidManifest_test.xml
@@ -46,7 +46,7 @@
</receiver>
<activity android:name=".opp.BluetoothOppLauncherActivity"
android:process="@string/process"
- android:theme="@android:style/Theme.Material.Light.Dialog"
+ android:theme="@*android:style/Theme.Material.DayNight.Dialog"
android:label="@string/bt_share_picker_label">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -76,7 +76,7 @@
</activity>
<activity android:name=".opp.BluetoothOppBtEnablingActivity"
android:process="@string/process"
- android:theme="@android:style/Theme.Material.Light.Dialog">
+ android:theme="@*android:style/Theme.Material.DayNight.Dialog">
</activity>
<activity android:name=".opp.BluetoothOppIncomingFileConfirmActivity"
android:process="@string/process">
@@ -87,7 +87,7 @@
<activity android:name=".pbap.BluetoothPbapActivity"
android:process="@string/process"
android:label=" "
- android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
+ android:theme="@*android:style/Theme.Material.DayNight.Dialog.Alert">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
diff --git a/jni/Android.mk b/jni/Android.mk
index fc4d871..55aa2cc 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -29,7 +29,7 @@
LOCAL_MULTILIB := 32
-#LOCAL_CFLAGS += -O0 -g
+LOCAL_CFLAGS += -Wall -Wextra -Wno-unused-parameter
LOCAL_MODULE := libbluetooth_jni
LOCAL_MODULE_TAGS := optional
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 415f6fe..b621ce4 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -96,40 +96,16 @@
static btav_callbacks_t sBluetoothA2dpCallbacks = {
sizeof(sBluetoothA2dpCallbacks),
bta2dp_connection_state_callback,
- bta2dp_audio_state_callback
+ bta2dp_audio_state_callback,
+ NULL, /* audio_config_cb */
};
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
- const bt_interface_t* btInf;
- bt_status_t status;
-
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
method_onAudioStateChanged =
env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
- /*
- if ( (btInf = getBluetoothInterface()) == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if ( (sBluetoothA2dpInterface = (btav_interface_t *)
- btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID)) == NULL) {
- ALOGE("Failed to get Bluetooth A2DP Interface");
- return;
- }
- */
-
- // TODO(BT) do this only once or
- // Do we need to do this every time the BT reenables?
- /*
- if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth A2DP, status: %d", status);
- sBluetoothA2dpInterface = NULL;
- return;
- }*/
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -172,7 +148,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -241,7 +216,7 @@
int register_com_android_bluetooth_a2dp(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/A2dpStateMachine",
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/A2dpStateMachine",
sMethods, NELEM(sMethods));
}
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index f2bbb1b..82110d3 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -120,14 +120,10 @@
sizeof(sBluetoothA2dpCallbacks),
bta2dp_connection_state_callback,
bta2dp_audio_state_callback,
- bta2dp_audio_config_callback
+ bta2dp_audio_config_callback,
};
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
- const bt_interface_t* btInf;
- bt_status_t status;
-
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
@@ -178,7 +174,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -237,17 +232,32 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static void informAudioFocusStateNative(JNIEnv *env, jobject object, jint focus_state) {
+ if (!sBluetoothA2dpInterface) return;
+
+ sBluetoothA2dpInterface->set_audio_focus_state((uint8_t)focus_state);
+}
+
+static void informAudioTrackGainNative(JNIEnv *env, jobject object, jfloat gain) {
+ if (!sBluetoothA2dpInterface) return;
+
+ sBluetoothA2dpInterface->set_audio_track_gain((float) gain);
+
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initNative", "()V", (void *) initNative},
{"cleanupNative", "()V", (void *) cleanupNative},
{"connectA2dpNative", "([B)Z", (void *) connectA2dpNative},
{"disconnectA2dpNative", "([B)Z", (void *) disconnectA2dpNative},
+ {"informAudioFocusStateNative", "(I)V", (void *) informAudioFocusStateNative},
+ {"informAudioTrackGainNative", "(F)V", (void *) informAudioTrackGainNative},
};
int register_com_android_bluetooth_a2dp_sink(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/A2dpSinkStateMachine",
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dpsink/A2dpSinkStateMachine",
sMethods, NELEM(sMethods));
}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 9e32721..2c07a57 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -28,6 +28,17 @@
namespace android {
static jmethodID method_handlePassthroughRsp;
static jmethodID method_onConnectionStateChanged;
+static jmethodID method_getRcFeatures;
+static jmethodID method_setplayerappsettingrsp;
+static jmethodID method_handleplayerappsetting;
+static jmethodID method_handleplayerappsettingchanged;
+static jmethodID method_handleSetAbsVolume;
+static jmethodID method_handleRegisterNotificationAbsVol;
+static jmethodID method_handletrackchanged;
+static jmethodID method_handleplaypositionchanged;
+static jmethodID method_handleplaystatuschanged;
+static jmethodID method_handleGroupNavigationRsp;
+
static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
static jobject mCallbacksObj = NULL;
@@ -59,6 +70,19 @@
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
+static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) {
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
+ (jint)pressed);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr) {
jbyteArray addr;
@@ -84,20 +108,361 @@
sCallbackEnv->DeleteLocalRef(addr);
}
+static void btavrcp_get_rcfeatures_callback(bt_bdaddr_t *bd_addr, int features) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_setplayerapplicationsetting_rsp_callback(bt_bdaddr_t *bd_addr,
+ uint8_t accepted) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_playerapplicationsetting_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
+ btrc_player_app_attr_t *app_attrs, uint8_t num_ext_attr,
+ btrc_player_app_ext_attr_t *ext_attrs) {
+ ALOGI("%s", __FUNCTION__);
+ jbyteArray addr;
+ jbyteArray playerattribs;
+ jint arraylen;
+ int i,k;
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+ /* TODO ext attrs
+ * Flattening defined attributes: <id,num_values,values[]>
+ */
+ arraylen = 0;
+ for (i = 0; i < num_attr; i++)
+ {
+ /*2 bytes for id and num */
+ arraylen += 2 + app_attrs[i].num_val;
+ }
+ ALOGI(" arraylen %d", arraylen);
+ playerattribs = sCallbackEnv->NewByteArray(arraylen);
+ if(!playerattribs)
+ {
+ ALOGE("Fail to new jbyteArray playerattribs ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+ k= 0;
+ for (i = 0; (i < num_attr)&&(k < arraylen); i++)
+ {
+ sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(app_attrs[i].attr_id));
+ k++;
+ sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(app_attrs[i].num_val));
+ k++;
+ sCallbackEnv->SetByteArrayRegion(playerattribs, k, app_attrs[i].num_val,
+ (jbyte*)(app_attrs[i].attr_val));
+ k = k + app_attrs[i].num_val;
+ }
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsetting, addr,
+ playerattribs, (jint)arraylen);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(playerattribs);
+}
+
+static void btavrcp_playerapplicationsetting_changed_callback(bt_bdaddr_t *bd_addr,
+ btrc_player_settings_t *p_vals) {
+
+ jbyteArray addr;
+ jbyteArray playerattribs;
+ int i, k, arraylen;
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+ arraylen = p_vals->num_attr*2;
+ playerattribs = sCallbackEnv->NewByteArray(arraylen);
+ if(!playerattribs)
+ {
+ ALOGE("Fail to new jbyteArray playerattribs ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+ /*
+ * Flatening format: <id,val>
+ */
+ k = 0;
+ for (i = 0; (i < p_vals->num_attr)&&( k < arraylen);i++)
+ {
+ sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_ids[i]));
+ k++;
+ sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_values[i]));
+ k++;
+ }
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsettingchanged, addr,
+ playerattribs, (jint)arraylen);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(playerattribs);
+}
+
+static void btavrcp_set_abs_vol_cmd_callback(bt_bdaddr_t *bd_addr, uint8_t abs_vol,
+ uint8_t label) {
+
+ jbyteArray addr;
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
+ (jbyte)label);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_register_notification_absvol_callback(bt_bdaddr_t *bd_addr, uint8_t label) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
+ (jbyte)label);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_track_changed_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
+ btrc_element_attr_val_t *p_attrs) {
+ /*
+ * byteArray will be formatted like this: id,len,string
+ * Assuming text feild to be null terminated.
+ */
+ jbyteArray addr;
+ jintArray attribIds;
+ jobjectArray stringArray;
+ jstring str;
+ jclass strclazz;
+ jint i;
+ ALOGI("%s", __FUNCTION__);
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ attribIds = sCallbackEnv->NewIntArray(num_attr);
+ if(!attribIds) {
+ ALOGE(" failed to set new array for attribIds");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
+
+ strclazz = sCallbackEnv->FindClass("java/lang/String");
+ stringArray = sCallbackEnv->NewObjectArray((jint)num_attr, strclazz, 0);
+ if(!stringArray) {
+ ALOGE(" failed to get String array");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ return;
+ }
+ for(i = 0; i < num_attr; i++)
+ {
+ str = sCallbackEnv->NewStringUTF((char*)(p_attrs[i].text));
+ if(!str) {
+ ALOGE(" Unable to get str ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ sCallbackEnv->DeleteLocalRef(stringArray);
+ return;
+ }
+ sCallbackEnv->SetIntArrayRegion(attribIds, i, 1, (jint*)&(p_attrs[i].attr_id));
+ sCallbackEnv->SetObjectArrayElement(stringArray, i,str);
+ sCallbackEnv->DeleteLocalRef(str);
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handletrackchanged, addr,
+ (jbyte)(num_attr), attribIds, stringArray);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ sCallbackEnv->DeleteLocalRef(attribIds);
+ /* TODO check do we need to delete str seperately or not */
+ sCallbackEnv->DeleteLocalRef(stringArray);
+ sCallbackEnv->DeleteLocalRef(strclazz);
+}
+
+static void btavrcp_play_position_changed_callback(bt_bdaddr_t *bd_addr, uint32_t song_len,
+ uint32_t song_pos) {
+
+ jbyteArray addr;
+ ALOGI("%s", __FUNCTION__);
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaypositionchanged, addr,
+ (jint)(song_len), (jint)song_pos);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_play_status_changed_callback(bt_bdaddr_t *bd_addr,
+ btrc_play_status_t play_status) {
+ jbyteArray addr;
+ ALOGI("%s", __FUNCTION__);
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if ((!addr)) {
+ ALOGE("Fail to get new array ");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaystatuschanged, addr,
+ (jbyte)play_status);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_passthrough_response_callback,
- btavrcp_connection_state_callback
+ btavrcp_groupnavigation_response_callback,
+ btavrcp_connection_state_callback,
+ btavrcp_get_rcfeatures_callback,
+ btavrcp_setplayerapplicationsetting_rsp_callback,
+ btavrcp_playerapplicationsetting_callback,
+ btavrcp_playerapplicationsetting_changed_callback,
+ btavrcp_set_abs_vol_cmd_callback,
+ btavrcp_register_notification_absvol_callback,
+ btavrcp_track_changed_callback,
+ btavrcp_play_position_changed_callback,
+ btavrcp_play_status_changed_callback
};
static void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
env->GetMethodID(clazz, "handlePassthroughRsp", "(II)V");
+ method_handleGroupNavigationRsp =
+ env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
+
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
+ method_getRcFeatures =
+ env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
+
+ method_setplayerappsettingrsp =
+ env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");
+
+ method_handleplayerappsetting =
+ env->GetMethodID(clazz, "handlePlayerAppSetting", "([B[BI)V");
+
+ method_handleplayerappsettingchanged =
+ env->GetMethodID(clazz, "onPlayerAppSettingChanged", "([B[BI)V");
+
+ method_handleSetAbsVolume =
+ env->GetMethodID(clazz, "handleSetAbsVolume", "([BBB)V");
+
+ method_handleRegisterNotificationAbsVol =
+ env->GetMethodID(clazz, "handleRegisterNotificationAbsVol", "([BB)V");
+
+ method_handletrackchanged =
+ env->GetMethodID(clazz, "onTrackChanged", "([BB[I[Ljava/lang/String;)V");
+
+ method_handleplaypositionchanged =
+ env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
+
+ method_handleplaystatuschanged =
+ env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -183,12 +548,140 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static jboolean sendGroupNavigationCommandNative(JNIEnv *env, jobject object, jbyteArray address,
+ jint key_code, jint key_state) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+
+ ALOGI("key_code: %d, key_state: %d", key_code, key_state);
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->send_group_navigation_cmd((bt_bdaddr_t *)addr,
+ (uint8_t)key_code, (uint8_t)key_state))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending Grp Navigation command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void setPlayerApplicationSettingValuesNative(JNIEnv *env, jobject object, jbyteArray address,
+ jbyte num_attrib, jbyteArray attrib_ids,
+ jbyteArray attrib_val) {
+ bt_status_t status;
+ jbyte *addr;
+ uint8_t *pAttrs = NULL;
+ uint8_t *pAttrsVal = NULL;
+ int i;
+ jbyte *attr;
+ jbyte *attr_val;
+
+ if (!sBluetoothAvrcpInterface) return;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ pAttrs = new uint8_t[num_attrib];
+ pAttrsVal = new uint8_t[num_attrib];
+ if ((!pAttrs) ||(!pAttrsVal)) {
+ delete[] pAttrs;
+ ALOGE("setPlayerApplicationSettingValuesNative: not have enough memeory");
+ return;
+ }
+ attr = env->GetByteArrayElements(attrib_ids, NULL);
+ attr_val = env->GetByteArrayElements(attrib_val, NULL);
+ if ((!attr)||(!attr_val)) {
+ delete[] pAttrs;
+ delete[] pAttrsVal;
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ for (i = 0; i < num_attrib; ++i) {
+ pAttrs[i] = (uint8_t)attr[i];
+ pAttrsVal[i] = (uint8_t)attr_val[i];
+ }
+ if (i < num_attrib) {
+ delete[] pAttrs;
+ delete[] pAttrsVal;
+ env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+ env->ReleaseByteArrayElements(attrib_val, attr_val, 0);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->set_player_app_setting_cmd((bt_bdaddr_t *)addr,
+ (uint8_t)num_attrib, pAttrs, pAttrsVal))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending setPlAppSettValNative command, status: %d", status);
+ }
+ delete[] pAttrs;
+ delete[] pAttrsVal;
+ env->ReleaseByteArrayElements(attrib_ids, attr, 0);
+ env->ReleaseByteArrayElements(attrib_val, attr_val, 0);
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void sendAbsVolRspNative(JNIEnv *env, jobject object, jbyteArray address,
+ jint abs_vol, jint label) {
+ bt_status_t status;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->set_volume_rsp((bt_bdaddr_t *)addr,
+ (uint8_t)abs_vol, (uint8_t)label))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending sendAbsVolRspNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void sendRegisterAbsVolRspNative(JNIEnv *env, jobject object, jbyteArray address,
+ jbyte rsp_type, jint abs_vol, jint label) {
+ bt_status_t status;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) return;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+ if ((status = sBluetoothAvrcpInterface->register_abs_vol_rsp((bt_bdaddr_t *)addr,
+ (btrc_notification_type_t)rsp_type,(uint8_t)abs_vol, (uint8_t)label))
+ != BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending sendRegisterAbsVolRspNative command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initNative", "()V", (void *) initNative},
{"cleanupNative", "()V", (void *) cleanupNative},
- {"sendPassThroughCommandNative", "([BII)Z",
- (void *) sendPassThroughCommandNative},
+ {"sendPassThroughCommandNative", "([BII)Z",(void *) sendPassThroughCommandNative},
+ {"sendGroupNavigationCommandNative", "([BII)Z",(void *) sendGroupNavigationCommandNative},
+ {"setPlayerApplicationSettingValuesNative", "([BB[B[B)V",
+ (void *) setPlayerApplicationSettingValuesNative},
+ {"sendAbsVolRspNative", "([BII)V",(void *) sendAbsVolRspNative},
+ {"sendRegisterAbsVolRspNative", "([BBII)V",(void *) sendRegisterAbsVolRspNative},
};
int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index b8e9a97..a8d11bb 100755
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -31,6 +31,8 @@
namespace android {
+#define OOB_TK_SIZE 16
+
#define ADDITIONAL_NREFS 50
static jmethodID method_stateChangeCallback;
static jmethodID method_adapterPropertyChangedCallback;
@@ -46,6 +48,11 @@
static jmethodID method_releaseWakeLock;
static jmethodID method_energyInfo;
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+} android_bluetooth_UidTraffic;
+
static const bt_interface_t *sBluetoothInterface = NULL;
static const btsock_interface_t *sBluetoothSocketInterface = NULL;
static JNIEnv *callbackEnv = NULL;
@@ -287,7 +294,6 @@
static void bond_state_changed_callback(bt_status_t status, bt_bdaddr_t *bd_addr,
bt_bond_state_t state) {
jbyteArray addr;
- int i;
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
@@ -313,7 +319,6 @@
bt_acl_state_t state)
{
jbyteArray addr;
- int i;
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
@@ -336,7 +341,6 @@
}
static void discovery_state_changed_callback(bt_discovery_state_t state) {
- jbyteArray addr;
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
@@ -352,7 +356,8 @@
static void pin_request_callback(bt_bdaddr_t *bd_addr, bt_bdname_t *bdname, uint32_t cod,
bool min_16_digits) {
- jbyteArray addr, devname;
+ jbyteArray addr = NULL;
+ jbyteArray devname = NULL;
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
@@ -387,7 +392,8 @@
static void ssp_request_callback(bt_bdaddr_t *bd_addr, bt_bdname_t *bdname, uint32_t cod,
bt_ssp_variant_t pairing_variant, uint32_t pass_key) {
- jbyteArray addr, devname;
+ jbyteArray addr = NULL;
+ jbyteArray devname = NULL;
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
@@ -447,18 +453,36 @@
ALOGV("%s: status:%d packet_count:%d ", __FUNCTION__, status, packet_count);
}
-static void energy_info_recv_callback(bt_activity_energy_info *p_energy_info)
+static void energy_info_recv_callback(bt_activity_energy_info *p_energy_info,
+ bt_uid_traffic_t* uid_data)
{
if (!checkCallbackThread()) {
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
return;
}
+ jsize len = 0;
+ for (bt_uid_traffic_t* data = uid_data; data->app_uid != -1; data++) {
+ len++;
+ }
+
+ jobjectArray array = callbackEnv->NewObjectArray(len, android_bluetooth_UidTraffic.clazz, NULL);
+ jsize i = 0;
+ for (bt_uid_traffic_t* data = uid_data; data->app_uid != -1; data++) {
+ jobject uidObj = callbackEnv->NewObject(android_bluetooth_UidTraffic.clazz,
+ android_bluetooth_UidTraffic.constructor,
+ (jint) data->app_uid, (jlong) data->rx_bytes,
+ (jlong) data->tx_bytes);
+ callbackEnv->SetObjectArrayElement(array, i++, uidObj);
+ callbackEnv->DeleteLocalRef(uidObj);
+ }
+
callbackEnv->CallVoidMethod(sJniAdapterServiceObj, method_energyInfo, p_energy_info->status,
p_energy_info->ctrl_state, p_energy_info->tx_time, p_energy_info->rx_time,
- p_energy_info->idle_time, p_energy_info->energy_used);
+ p_energy_info->idle_time, p_energy_info->energy_used, array);
checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
+ callbackEnv->DeleteLocalRef(array);
}
static bt_callbacks_t sBluetoothCallbacks = {
@@ -486,7 +510,7 @@
static JavaVMAttachArgs sAttachArgs = {
.version = JNI_VERSION_1_6,
- .name = "bluedroid wake/alarm thread",
+ .name = "bluetooth wake",
.group = NULL
};
@@ -530,54 +554,62 @@
jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
if (status != JNI_OK && status != JNI_EDETACHED) {
ALOGE("%s unable to get environment for JNI call", __func__);
- return BT_STATUS_FAIL;
+ return BT_STATUS_JNI_ENVIRONMENT_ERROR;
}
if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, &sAttachArgs) != 0) {
ALOGE("%s unable to attach thread to VM", __func__);
- return BT_STATUS_FAIL;
+ return BT_STATUS_JNI_THREAD_ATTACH_ERROR;
}
- jboolean ret = JNI_FALSE;
+ jint ret = BT_STATUS_SUCCESS;
jstring lock_name_jni = env->NewStringUTF(lock_name);
if (lock_name_jni) {
- ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni);
+ bool acquired = env->CallBooleanMethod(sJniAdapterServiceObj,
+ method_acquireWakeLock, lock_name_jni);
+ if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
env->DeleteLocalRef(lock_name_jni);
} else {
ALOGE("%s unable to allocate string: %s", __func__, lock_name);
+ ret = BT_STATUS_NOMEM;
}
if (status == JNI_EDETACHED) {
vm->DetachCurrentThread();
}
- return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
+ return ret;
}
static int release_wake_lock_callout(const char *lock_name) {
JNIEnv *env;
JavaVM *vm = AndroidRuntime::getJavaVM();
jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
-
if (status != JNI_OK && status != JNI_EDETACHED) {
ALOGE("%s unable to get environment for JNI call", __func__);
- return BT_STATUS_FAIL;
+ return BT_STATUS_JNI_ENVIRONMENT_ERROR;
}
if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, &sAttachArgs) != 0) {
ALOGE("%s unable to attach thread to VM", __func__);
- return BT_STATUS_FAIL;
+ return BT_STATUS_JNI_THREAD_ATTACH_ERROR;
}
- jboolean ret = JNI_FALSE;
+
+ jint ret = BT_STATUS_SUCCESS;
jstring lock_name_jni = env->NewStringUTF(lock_name);
if (lock_name_jni) {
- ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni);
+ bool released = env->CallBooleanMethod(sJniAdapterServiceObj,
+ method_releaseWakeLock, lock_name_jni);
+ if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
env->DeleteLocalRef(lock_name_jni);
} else {
ALOGE("%s unable to allocate string: %s", __func__, lock_name);
+ ret = BT_STATUS_NOMEM;
}
+
if (status == JNI_EDETACHED) {
vm->DetachCurrentThread();
}
- return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
+
+ return ret;
}
// Called by Java code when alarm is fired. A wake lock is held by the caller
@@ -603,6 +635,11 @@
int err;
hw_module_t* module;
+
+ jclass jniUidTrafficClass = env->FindClass("android/bluetooth/UidTraffic");
+ android_bluetooth_UidTraffic.constructor = env->GetMethodID(jniUidTrafficClass,
+ "<init>", "(IJJ)V");
+
jclass jniCallbackClass =
env->FindClass("com/android/bluetooth/btservice/JniCallbacks");
sJniCallbacksField = env->GetFieldID(clazz, "mJniCallbacks",
@@ -634,7 +671,7 @@
method_setWakeAlarm = env->GetMethodID(clazz, "setWakeAlarm", "(JZ)Z");
method_acquireWakeLock = env->GetMethodID(clazz, "acquireWakeLock", "(Ljava/lang/String;)Z");
method_releaseWakeLock = env->GetMethodID(clazz, "releaseWakeLock", "(Ljava/lang/String;)Z");
- method_energyInfo = env->GetMethodID(clazz, "energyInfoCallback", "(IIJJJJ)V");
+ method_energyInfo = env->GetMethodID(clazz, "energyInfoCallback", "(IIJJJJ[Landroid/bluetooth/UidTraffic;)V");
char value[PROPERTY_VALUE_MAX];
property_get("bluetooth.mock_stack", value, "");
@@ -660,6 +697,9 @@
static bool initNative(JNIEnv* env, jobject obj) {
ALOGV("%s:",__FUNCTION__);
+ android_bluetooth_UidTraffic.clazz = (jclass) env->NewGlobalRef(
+ env->FindClass("android/bluetooth/UidTraffic"));
+
sJniAdapterServiceObj = env->NewGlobalRef(obj);
sJniCallbacksObj = env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));
@@ -699,16 +739,17 @@
env->DeleteGlobalRef(sJniCallbacksObj);
env->DeleteGlobalRef(sJniAdapterServiceObj);
+ env->DeleteGlobalRef(android_bluetooth_UidTraffic.clazz);
+ android_bluetooth_UidTraffic.clazz = NULL;
return JNI_TRUE;
}
-static jboolean enableNative(JNIEnv* env, jobject obj) {
+static jboolean enableNative(JNIEnv* env, jobject obj, jboolean isGuest) {
ALOGV("%s:",__FUNCTION__);
jboolean result = JNI_FALSE;
if (!sBluetoothInterface) return result;
-
- int ret = sBluetoothInterface->enable();
+ int ret = sBluetoothInterface->enable(isGuest == JNI_TRUE ? 1 : 0);
result = (ret == BT_STATUS_SUCCESS || ret == BT_STATUS_DONE) ? JNI_TRUE : JNI_FALSE;
return result;
}
@@ -771,6 +812,56 @@
return result;
}
+static jbyteArray callByteArrayGetter(JNIEnv* env, jobject object,
+ const char* className,
+ const char* methodName) {
+ jclass myClass = env->FindClass(className);
+ jmethodID myMethod = env->GetMethodID(myClass, methodName, "()[B");
+ return (jbyteArray) env->CallObjectMethod(object, myMethod);
+}
+
+static jboolean createBondOutOfBandNative(JNIEnv* env, jobject obj, jbyteArray address,
+ jint transport, jobject oobData) {
+ jbyte *addr;
+ jboolean result = JNI_FALSE;
+ bt_out_of_band_data_t oob_data;
+
+ memset(&oob_data, 0, sizeof(oob_data));
+
+ if (!sBluetoothInterface) return result;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (addr == NULL) {
+ jniThrowIOException(env, EINVAL);
+ return result;
+ }
+
+ jbyte* smTKBytes = NULL;
+ jbyteArray smTK = callByteArrayGetter(env, oobData, "android/bluetooth/OobData", "getSecurityManagerTk");
+ if (smTK != NULL) {
+ smTKBytes = env->GetByteArrayElements(smTK, NULL);
+ int len = env->GetArrayLength(smTK);
+ if (len != OOB_TK_SIZE) {
+ ALOGI("%s: wrong length of smTK, should be empty or %d bytes.", __FUNCTION__, OOB_TK_SIZE);
+ jniThrowIOException(env, EINVAL);
+ goto done;
+ }
+ memcpy(oob_data.sm_tk, smTKBytes, len);
+ }
+
+ if (sBluetoothInterface->create_bond_out_of_band((bt_bdaddr_t *)addr, transport, &oob_data)
+ == BT_STATUS_SUCCESS)
+ result = JNI_TRUE;
+
+done:
+ env->ReleaseByteArrayElements(address, addr, 0);
+
+ if (smTK != NULL)
+ env->ReleaseByteArrayElements(smTK, smTKBytes, 0);
+
+ return result;
+}
+
static jboolean removeBondNative(JNIEnv* env, jobject obj, jbyteArray address) {
ALOGV("%s:",__FUNCTION__);
@@ -1002,7 +1093,7 @@
}
static int connectSocketNative(JNIEnv *env, jobject object, jbyteArray address, jint type,
- jbyteArray uuidObj, jint channel, jint flag) {
+ jbyteArray uuidObj, jint channel, jint flag, jint callingUid) {
jbyte *addr = NULL, *uuid = NULL;
int socket_fd;
bt_status_t status;
@@ -1024,7 +1115,8 @@
}
if ( (status = sBluetoothSocketInterface->connect((bt_bdaddr_t *) addr, (btsock_type_t) type,
- (const uint8_t*) uuid, channel, &socket_fd, flag)) != BT_STATUS_SUCCESS) {
+ (const uint8_t*) uuid, channel, &socket_fd, flag, callingUid))
+ != BT_STATUS_SUCCESS) {
ALOGE("Socket connection failed: %d", status);
goto Fail;
}
@@ -1047,7 +1139,7 @@
static int createSocketChannelNative(JNIEnv *env, jobject object, jint type,
jstring name_str, jbyteArray uuidObj,
- jint channel, jint flag) {
+ jint channel, jint flag, jint callingUid) {
const char *service_name = NULL;
jbyte *uuid = NULL;
int socket_fd;
@@ -1069,7 +1161,8 @@
}
}
if ( (status = sBluetoothSocketInterface->listen((btsock_type_t) type, service_name,
- (const uint8_t*) uuid, channel, &socket_fd, flag)) != BT_STATUS_SUCCESS) {
+ (const uint8_t*) uuid, channel, &socket_fd, flag, callingUid))
+ != BT_STATUS_SUCCESS) {
ALOGE("Socket listen failed: %d", status);
goto Fail;
}
@@ -1112,7 +1205,8 @@
return result;
}
-static void dumpNative(JNIEnv *env, jobject obj, jobject fdObj)
+static void dumpNative(JNIEnv *env, jobject obj, jobject fdObj,
+ jobjectArray argArray)
{
ALOGV("%s()", __FUNCTION__);
if (!sBluetoothInterface) return;
@@ -1120,7 +1214,26 @@
int fd = jniGetFDFromFileDescriptor(env, fdObj);
if (fd < 0) return;
- sBluetoothInterface->dump(fd);
+ int numArgs = env->GetArrayLength(argArray);
+
+ jstring *argObjs = new jstring[numArgs];
+ const char **args = nullptr;
+ if (numArgs > 0)
+ args = new const char*[numArgs];
+
+ for (int i = 0; i < numArgs; i++) {
+ argObjs[i] = (jstring) env->GetObjectArrayElement(argArray, i);
+ args[i] = env->GetStringUTFChars(argObjs[i], NULL);
+ }
+
+ sBluetoothInterface->dump(fd, args);
+
+ for (int i = 0; i < numArgs; i++) {
+ env->ReleaseStringUTFChars(argObjs[i], args[i]);
+ }
+
+ delete[] args;
+ delete[] argObjs;
}
static jboolean factoryResetNative(JNIEnv *env, jobject obj) {
@@ -1156,7 +1269,7 @@
{"classInitNative", "()V", (void *) classInitNative},
{"initNative", "()Z", (void *) initNative},
{"cleanupNative", "()V", (void*) cleanupNative},
- {"enableNative", "()Z", (void*) enableNative},
+ {"enableNative", "(Z)Z", (void*) enableNative},
{"disableNative", "()Z", (void*) disableNative},
{"setAdapterPropertyNative", "(I[B)Z", (void*) setAdapterPropertyNative},
{"getAdapterPropertiesNative", "()Z", (void*) getAdapterPropertiesNative},
@@ -1166,19 +1279,20 @@
{"startDiscoveryNative", "()Z", (void*) startDiscoveryNative},
{"cancelDiscoveryNative", "()Z", (void*) cancelDiscoveryNative},
{"createBondNative", "([BI)Z", (void*) createBondNative},
+ {"createBondOutOfBandNative", "([BILandroid/bluetooth/OobData;)Z", (void*) createBondOutOfBandNative},
{"removeBondNative", "([B)Z", (void*) removeBondNative},
{"cancelBondNative", "([B)Z", (void*) cancelBondNative},
{"getConnectionStateNative", "([B)I", (void*) getConnectionStateNative},
{"pinReplyNative", "([BZI[B)Z", (void*) pinReplyNative},
{"sspReplyNative", "([BIZI)Z", (void*) sspReplyNative},
{"getRemoteServicesNative", "([B)Z", (void*) getRemoteServicesNative},
- {"connectSocketNative", "([BI[BII)I", (void*) connectSocketNative},
- {"createSocketChannelNative", "(ILjava/lang/String;[BII)I",
+ {"connectSocketNative", "([BI[BIII)I", (void*) connectSocketNative},
+ {"createSocketChannelNative", "(ILjava/lang/String;[BIII)I",
(void*) createSocketChannelNative},
{"configHciSnoopLogNative", "(Z)Z", (void*) configHciSnoopLogNative},
{"alarmFiredNative", "()V", (void *) alarmFiredNative},
{"readEnergyInfo", "()I", (void*) readEnergyInfo},
- {"dumpNative", "(Ljava/io/FileDescriptor;)V", (void*) dumpNative},
+ {"dumpNative", "(Ljava/io/FileDescriptor;[Ljava/lang/String;)V", (void*) dumpNative},
{"factoryResetNative", "()Z", (void*)factoryResetNative},
{"interopDatabaseClearNative", "()V", (void*) interopDatabaseClearNative},
{"interopDatabaseAddNative", "(I[BI)V", (void*) interopDatabaseAddNative}
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index d11623a..582d63c 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -63,7 +63,7 @@
}
}
-static uint64_t uuid_lsb(bt_uuid_t* uuid)
+static uint64_t uuid_lsb(const bt_uuid_t* uuid)
{
uint64_t lsb = 0;
int i;
@@ -77,7 +77,7 @@
return lsb;
}
-static uint64_t uuid_msb(bt_uuid_t* uuid)
+static uint64_t uuid_msb(const bt_uuid_t* uuid)
{
uint64_t msb = 0;
int i;
@@ -148,13 +148,9 @@
static jmethodID method_onWriteCharacteristic;
static jmethodID method_onExecuteCompleted;
static jmethodID method_onSearchCompleted;
-static jmethodID method_onSearchResult;
static jmethodID method_onReadDescriptor;
static jmethodID method_onWriteDescriptor;
static jmethodID method_onNotify;
-static jmethodID method_onGetCharacteristic;
-static jmethodID method_onGetDescriptor;
-static jmethodID method_onGetIncludedService;
static jmethodID method_onRegisterForNotifications;
static jmethodID method_onReadRemoteRssi;
static jmethodID method_onAdvertiseCallback;
@@ -175,6 +171,8 @@
static jmethodID method_CreateonTrackAdvFoundLostObject;
static jmethodID method_onTrackAdvFoundLost;
static jmethodID method_onScanParamSetupCompleted;
+static jmethodID method_getSampleGattDbElement;
+static jmethodID method_onGetGattDb;
/**
* Server callback methods
@@ -284,51 +282,11 @@
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
-void btgattc_search_result_cb(int conn_id, btgatt_srvc_id_t *srvc_id)
+void btgattc_register_for_notification_cb(int conn_id, int registered, int status, uint16_t handle)
{
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSearchResult, conn_id,
- SRVC_ID_PARAMS(srvc_id));
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
-}
-
-void btgattc_get_characteristic_cb(int conn_id, int status,
- btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id,
- int char_prop)
-{
- CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetCharacteristic
- , conn_id, status, SRVC_ID_PARAMS(srvc_id), GATT_ID_PARAMS(char_id)
- , char_prop);
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
-}
-
-void btgattc_get_descriptor_cb(int conn_id, int status,
- btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id,
- btgatt_gatt_id_t *descr_id)
-{
- CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetDescriptor
- , conn_id, status, SRVC_ID_PARAMS(srvc_id), GATT_ID_PARAMS(char_id)
- , GATT_ID_PARAMS(descr_id));
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
-}
-
-void btgattc_get_included_service_cb(int conn_id, int status,
- btgatt_srvc_id_t *srvc_id, btgatt_srvc_id_t *incl_srvc_id)
-{
- CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetIncludedService
- , conn_id, status, SRVC_ID_PARAMS(srvc_id), SRVC_ID_PARAMS(incl_srvc_id));
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
-}
-
-void btgattc_register_for_notification_cb(int conn_id, int registered, int status,
- btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id)
-{
- CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRegisterForNotifications
- , conn_id, status, registered, SRVC_ID_PARAMS(srvc_id), GATT_ID_PARAMS(char_id));
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRegisterForNotifications,
+ conn_id, status, registered, handle);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
@@ -346,8 +304,7 @@
sCallbackEnv->SetByteArrayRegion(jb, 0, p_data->len, (jbyte *) p_data->value);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNotify
- , conn_id, address, SRVC_ID_PARAMS((&p_data->srvc_id))
- , GATT_ID_PARAMS((&p_data->char_id)), p_data->is_notify, jb);
+ , conn_id, address, p_data->handle, p_data->is_notify, jb);
sCallbackEnv->DeleteLocalRef(address);
sCallbackEnv->DeleteLocalRef(jb);
@@ -370,19 +327,17 @@
sCallbackEnv->SetByteArrayRegion(jb, 0, 1, (jbyte *) &value);
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadCharacteristic
- , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
- , GATT_ID_PARAMS((&p_data->char_id)), p_data->value_type, jb);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadCharacteristic,
+ conn_id, status, p_data->handle, jb);
sCallbackEnv->DeleteLocalRef(jb);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
-void btgattc_write_characteristic_cb(int conn_id, int status, btgatt_write_params_t *p_data)
+void btgattc_write_characteristic_cb(int conn_id, int status, uint16_t handle)
{
CHECK_CALLBACK_ENV
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteCharacteristic
- , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
- , GATT_ID_PARAMS((&p_data->char_id)));
+ , conn_id, status, handle);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
@@ -408,22 +363,18 @@
jb = sCallbackEnv->NewByteArray(1);
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadDescriptor
- , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
- , GATT_ID_PARAMS((&p_data->char_id)), GATT_ID_PARAMS((&p_data->descr_id))
- , p_data->value_type, jb);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadDescriptor,
+ conn_id, status, p_data->handle, jb);
sCallbackEnv->DeleteLocalRef(jb);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
-void btgattc_write_descriptor_cb(int conn_id, int status, btgatt_write_params_t *p_data)
+void btgattc_write_descriptor_cb(int conn_id, int status, uint16_t handle)
{
CHECK_CALLBACK_ENV
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteDescriptor
- , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
- , GATT_ID_PARAMS((&p_data->char_id))
- , GATT_ID_PARAMS((&p_data->descr_id)));
+ , conn_id, status, handle);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
@@ -599,16 +550,70 @@
checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
}
+void btgattc_get_gatt_db_cb(int conn_id, btgatt_db_element_t *db, int count)
+{
+ CHECK_CALLBACK_ENV
+
+ // Because JNI uses a different class loader in the callback context, we cannot simply get the class.
+ // As a workaround, we have to make sure we obtain an object of the class first, as this will cause
+ // class loader to load it.
+ jobject objectForClass = sCallbackEnv->CallObjectMethod(mCallbacksObj, method_getSampleGattDbElement);
+ jclass gattDbElementClazz = sCallbackEnv->GetObjectClass(objectForClass);
+ sCallbackEnv->DeleteLocalRef(objectForClass);
+
+ jmethodID gattDbElementConstructor = sCallbackEnv->GetMethodID(gattDbElementClazz, "<init>", "()V");
+
+ jclass arrayListclazz = sCallbackEnv->FindClass("java/util/ArrayList");
+ jobject array = sCallbackEnv->NewObject(arrayListclazz, sCallbackEnv->GetMethodID(arrayListclazz, "<init>", "()V"));
+ jmethodID arrayAdd = sCallbackEnv->GetMethodID(arrayListclazz, "add", "(Ljava/lang/Object;)Z");
+
+ jclass uuidClazz = sCallbackEnv->FindClass("java/util/UUID");
+ jmethodID uuidConstructor = sCallbackEnv->GetMethodID(uuidClazz, "<init>", "(JJ)V");
+
+ for (int i = 0; i < count; i++) {
+ const btgatt_db_element_t &curr = db[i];
+
+ jobject element = sCallbackEnv->NewObject(gattDbElementClazz, gattDbElementConstructor);
+
+ jfieldID fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "id", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.id);
+
+ jobject uuid = sCallbackEnv->NewObject(uuidClazz, uuidConstructor, uuid_msb(&curr.uuid), uuid_lsb(&curr.uuid));
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "uuid", "java/util/UUID");
+ sCallbackEnv->SetObjectField(element, fid, uuid);
+ sCallbackEnv->DeleteLocalRef(uuid);
+
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "type", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.type);
+
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "attributeHandle", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.attribute_handle);
+
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "startHandle", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.start_handle);
+
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "endHandle", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.end_handle);
+
+ fid = sCallbackEnv->GetFieldID(gattDbElementClazz, "properties", "I");
+ sCallbackEnv->SetIntField(element, fid, curr.properties);
+
+ sCallbackEnv->CallBooleanMethod(array, arrayAdd, element);
+ sCallbackEnv->DeleteLocalRef(element);
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetGattDb, conn_id, array);
+ sCallbackEnv->DeleteLocalRef(array);
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
static const btgatt_client_callbacks_t sGattClientCallbacks = {
btgattc_register_app_cb,
btgattc_scan_result_cb,
btgattc_open_cb,
btgattc_close_cb,
btgattc_search_complete_cb,
- btgattc_search_result_cb,
- btgattc_get_characteristic_cb,
- btgattc_get_descriptor_cb,
- btgattc_get_included_service_cb,
btgattc_register_for_notification_cb,
btgattc_notify_cb,
btgattc_read_characteristic_cb,
@@ -632,7 +637,10 @@
btgattc_batchscan_reports_cb,
btgattc_batchscan_threshold_cb,
btgattc_track_adv_event_cb,
- btgattc_scan_parameter_setup_completed_cb
+ btgattc_scan_parameter_setup_completed_cb,
+ btgattc_get_gatt_db_cb,
+ NULL, /* services_removed_cb */
+ NULL /* services_added_cb */
};
@@ -858,18 +866,14 @@
method_onScanResult = env->GetMethodID(clazz, "onScanResult", "(Ljava/lang/String;I[B)V");
method_onConnected = env->GetMethodID(clazz, "onConnected", "(IIILjava/lang/String;)V");
method_onDisconnected = env->GetMethodID(clazz, "onDisconnected", "(IIILjava/lang/String;)V");
- method_onReadCharacteristic = env->GetMethodID(clazz, "onReadCharacteristic", "(IIIIJJIJJI[B)V");
- method_onWriteCharacteristic = env->GetMethodID(clazz, "onWriteCharacteristic", "(IIIIJJIJJ)V");
+ method_onReadCharacteristic = env->GetMethodID(clazz, "onReadCharacteristic", "(III[B)V");
+ method_onWriteCharacteristic = env->GetMethodID(clazz, "onWriteCharacteristic", "(III)V");
method_onExecuteCompleted = env->GetMethodID(clazz, "onExecuteCompleted", "(II)V");
method_onSearchCompleted = env->GetMethodID(clazz, "onSearchCompleted", "(II)V");
- method_onSearchResult = env->GetMethodID(clazz, "onSearchResult", "(IIIJJ)V");
- method_onReadDescriptor = env->GetMethodID(clazz, "onReadDescriptor", "(IIIIJJIJJIJJI[B)V");
- method_onWriteDescriptor = env->GetMethodID(clazz, "onWriteDescriptor", "(IIIIJJIJJIJJ)V");
- method_onNotify = env->GetMethodID(clazz, "onNotify", "(ILjava/lang/String;IIJJIJJZ[B)V");
- method_onGetCharacteristic = env->GetMethodID(clazz, "onGetCharacteristic", "(IIIIJJIJJI)V");
- method_onGetDescriptor = env->GetMethodID(clazz, "onGetDescriptor", "(IIIIJJIJJIJJ)V");
- method_onGetIncludedService = env->GetMethodID(clazz, "onGetIncludedService", "(IIIIJJIIJJ)V");
- method_onRegisterForNotifications = env->GetMethodID(clazz, "onRegisterForNotifications", "(IIIIIJJIJJ)V");
+ method_onReadDescriptor = env->GetMethodID(clazz, "onReadDescriptor", "(III[B)V");
+ method_onWriteDescriptor = env->GetMethodID(clazz, "onWriteDescriptor", "(III)V");
+ method_onNotify = env->GetMethodID(clazz, "onNotify", "(ILjava/lang/String;IZ[B)V");
+ method_onRegisterForNotifications = env->GetMethodID(clazz, "onRegisterForNotifications", "(IIII)V");
method_onReadRemoteRssi = env->GetMethodID(clazz, "onReadRemoteRssi", "(ILjava/lang/String;II)V");
method_onConfigureMTU = env->GetMethodID(clazz, "onConfigureMTU", "(III)V");
method_onAdvertiseCallback = env->GetMethodID(clazz, "onAdvertiseCallback", "(II)V");
@@ -889,6 +893,8 @@
method_onTrackAdvFoundLost = env->GetMethodID(clazz, "onTrackAdvFoundLost",
"(Lcom/android/bluetooth/gatt/AdvtFilterOnFoundOnLostInfo;)V");
method_onScanParamSetupCompleted = env->GetMethodID(clazz, "onScanParamSetupCompleted", "(II)V");
+ method_getSampleGattDbElement = env->GetMethodID(clazz, "GetSampleGattDbElement", "()Lcom/android/bluetooth/gatt/GattDbElement;");
+ method_onGetGattDb = env->GetMethodID(clazz, "onGetGattDb", "(ILjava/util/ArrayList;)V");
// Server callbacks
@@ -952,7 +958,6 @@
}
static void cleanupNative(JNIEnv *env, jobject object) {
- bt_status_t status;
if (!btIf) return;
if (sGattIf != NULL) {
@@ -1040,145 +1045,32 @@
sGattIf->client->search_service(conn_id, search_all ? 0 : &uuid);
}
-static void gattClientGetCharacteristicNative(JNIEnv* env, jobject object,
- jint conn_id,
- jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb)
+static void gattClientGetGattDbNative(JNIEnv* env, jobject object,
+ jint conn_id)
{
if (!sGattIf) return;
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
-
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
- if (char_id_uuid_lsb == 0)
- {
- sGattIf->client->get_characteristic(conn_id, &srvc_id, 0);
- } else {
- sGattIf->client->get_characteristic(conn_id, &srvc_id, &char_id);
- }
-}
-
-static void gattClientGetDescriptorNative(JNIEnv* env, jobject object,
- jint conn_id,
- jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jint descr_id_inst_id,
- jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb)
-{
- if (!sGattIf) return;
-
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
- btgatt_gatt_id_t descr_id;
- descr_id.inst_id = (uint8_t) descr_id_inst_id;
- set_uuid(descr_id.uuid.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
-
- if (descr_id_uuid_lsb == 0)
- {
- sGattIf->client->get_descriptor(conn_id, &srvc_id, &char_id, 0);
- } else {
- sGattIf->client->get_descriptor(conn_id, &srvc_id, &char_id, &descr_id);
- }
-}
-
-static void gattClientGetIncludedServiceNative(JNIEnv* env, jobject object,
- jint conn_id, jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint incl_service_id_inst_id, jint incl_service_type,
- jlong incl_service_id_uuid_lsb, jlong incl_service_id_uuid_msb)
-{
- if (!sGattIf) return;
-
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_srvc_id_t incl_srvc_id;
- incl_srvc_id.id.inst_id = (uint8_t) incl_service_id_inst_id;
- incl_srvc_id.is_primary = (incl_service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(incl_srvc_id.id.uuid.uu, incl_service_id_uuid_msb, incl_service_id_uuid_lsb);
-
- if (incl_service_id_uuid_lsb == 0)
- {
- sGattIf->client->get_included_service(conn_id, &srvc_id, 0);
- } else {
- sGattIf->client->get_included_service(conn_id, &srvc_id, &incl_srvc_id);
- }
+ sGattIf->client->get_gatt_db(conn_id);
}
static void gattClientReadCharacteristicNative(JNIEnv* env, jobject object,
- jint conn_id, jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jint authReq)
+ jint conn_id, jint handle, jint authReq)
{
if (!sGattIf) return;
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
- sGattIf->client->read_characteristic(conn_id, &srvc_id, &char_id, authReq);
+ sGattIf->client->read_characteristic(conn_id, handle, authReq);
}
static void gattClientReadDescriptorNative(JNIEnv* env, jobject object,
- jint conn_id, jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jint descr_id_inst_id,
- jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb,
- jint authReq)
+ jint conn_id, jint handle, jint authReq)
{
if (!sGattIf) return;
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
- btgatt_gatt_id_t descr_id;
- descr_id.inst_id = (uint8_t) descr_id_inst_id;
- set_uuid(descr_id.uuid.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
-
- sGattIf->client->read_descriptor(conn_id, &srvc_id, &char_id, &descr_id, authReq);
+ sGattIf->client->read_descriptor(conn_id, handle, authReq);
}
static void gattClientWriteCharacteristicNative(JNIEnv* env, jobject object,
- jint conn_id, jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jint write_type, jint auth_req, jbyteArray value)
+ jint conn_id, jint handle, jint write_type, jint auth_req, jbyteArray value)
{
if (!sGattIf) return;
@@ -1187,21 +1079,11 @@
return;
}
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
uint16_t len = (uint16_t) env->GetArrayLength(value);
jbyte *p_value = env->GetByteArrayElements(value, NULL);
if (p_value == NULL) return;
- sGattIf->client->write_characteristic(conn_id, &srvc_id, &char_id,
- write_type, len, auth_req, (char*)p_value);
+ sGattIf->client->write_characteristic(conn_id, handle, write_type, len, auth_req, (char*)p_value);
env->ReleaseByteArrayElements(value, p_value, 0);
}
@@ -1213,13 +1095,7 @@
}
static void gattClientWriteDescriptorNative(JNIEnv* env, jobject object,
- jint conn_id, jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jint descr_id_inst_id,
- jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb,
- jint write_type, jint auth_req, jbyteArray value)
+ jint conn_id, jint handle, jint write_type, jint auth_req, jbyteArray value)
{
if (!sGattIf) return;
@@ -1228,55 +1104,27 @@
return;
}
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
- btgatt_gatt_id_t descr_id;
- descr_id.inst_id = (uint8_t) descr_id_inst_id;
- set_uuid(descr_id.uuid.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
-
uint16_t len = (uint16_t) env->GetArrayLength(value);
jbyte *p_value = env->GetByteArrayElements(value, NULL);
if (p_value == NULL) return;
- sGattIf->client->write_descriptor(conn_id, &srvc_id, &char_id, &descr_id,
- write_type, len, auth_req, (char*)p_value);
+ sGattIf->client->write_descriptor(conn_id, handle, write_type, len, auth_req, (char*)p_value);
env->ReleaseByteArrayElements(value, p_value, 0);
}
static void gattClientRegisterForNotificationsNative(JNIEnv* env, jobject object,
- jint clientIf, jstring address,
- jint service_type, jint service_id_inst_id,
- jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
- jint char_id_inst_id,
- jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
- jboolean enable)
+ jint clientIf, jstring address, jint handle, jboolean enable)
{
if (!sGattIf) return;
- btgatt_srvc_id_t srvc_id;
- srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
- srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
- set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
-
- btgatt_gatt_id_t char_id;
- char_id.inst_id = (uint8_t) char_id_inst_id;
- set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
-
bt_bdaddr_t bd_addr;
const char *c_address = env->GetStringUTFChars(address, NULL);
bd_addr_str_to_addr(c_address, bd_addr.address);
if (enable)
- sGattIf->client->register_for_notification(clientIf, &bd_addr, &srvc_id, &char_id);
+ sGattIf->client->register_for_notification(clientIf, &bd_addr, handle);
else
- sGattIf->client->deregister_for_notification(clientIf, &bd_addr, &srvc_id, &char_id);
+ sGattIf->client->deregister_for_notification(clientIf, &bd_addr, handle);
}
static void gattClientReadRemoteRssiNative(JNIEnv* env, jobject object, jint clientif,
@@ -1849,15 +1697,13 @@
{"gattClientDisconnectNative", "(ILjava/lang/String;I)V", (void *) gattClientDisconnectNative},
{"gattClientRefreshNative", "(ILjava/lang/String;)V", (void *) gattClientRefreshNative},
{"gattClientSearchServiceNative", "(IZJJ)V", (void *) gattClientSearchServiceNative},
- {"gattClientGetCharacteristicNative", "(IIIJJIJJ)V", (void *) gattClientGetCharacteristicNative},
- {"gattClientGetDescriptorNative", "(IIIJJIJJIJJ)V", (void *) gattClientGetDescriptorNative},
- {"gattClientGetIncludedServiceNative", "(IIIJJIIJJ)V", (void *) gattClientGetIncludedServiceNative},
- {"gattClientReadCharacteristicNative", "(IIIJJIJJI)V", (void *) gattClientReadCharacteristicNative},
- {"gattClientReadDescriptorNative", "(IIIJJIJJIJJI)V", (void *) gattClientReadDescriptorNative},
- {"gattClientWriteCharacteristicNative", "(IIIJJIJJII[B)V", (void *) gattClientWriteCharacteristicNative},
- {"gattClientWriteDescriptorNative", "(IIIJJIJJIJJII[B)V", (void *) gattClientWriteDescriptorNative},
+ {"gattClientGetGattDbNative", "(I)V", (void *) gattClientGetGattDbNative},
+ {"gattClientReadCharacteristicNative", "(III)V", (void *) gattClientReadCharacteristicNative},
+ {"gattClientReadDescriptorNative", "(III)V", (void *) gattClientReadDescriptorNative},
+ {"gattClientWriteCharacteristicNative", "(IIII[B)V", (void *) gattClientWriteCharacteristicNative},
+ {"gattClientWriteDescriptorNative", "(IIII[B)V", (void *) gattClientWriteDescriptorNative},
{"gattClientExecuteWriteNative", "(IZ)V", (void *) gattClientExecuteWriteNative},
- {"gattClientRegisterForNotificationsNative", "(ILjava/lang/String;IIJJIJJZ)V", (void *) gattClientRegisterForNotificationsNative},
+ {"gattClientRegisterForNotificationsNative", "(ILjava/lang/String;IZ)V", (void *) gattClientRegisterForNotificationsNative},
{"gattClientReadRemoteRssiNative", "(ILjava/lang/String;)V", (void *) gattClientReadRemoteRssiNative},
{"gattClientConfigureMTUNative", "(II)V", (void *) gattClientConfigureMTUNative},
{"gattConnectionParameterUpdateNative", "(ILjava/lang/String;IIII)V", (void *) gattConnectionParameterUpdateNative},
diff --git a/jni/com_android_bluetooth_hdp.cpp b/jni/com_android_bluetooth_hdp.cpp
index 81c8312..edbbec8 100644
--- a/jni/com_android_bluetooth_hdp.cpp
+++ b/jni/com_android_bluetooth_hdp.cpp
@@ -98,35 +98,9 @@
// Define native functions
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
-// const bt_interface_t* btInf;
-// bt_status_t status;
-
method_onAppRegistrationState = env->GetMethodID(clazz, "onAppRegistrationState", "(II)V");
method_onChannelStateChanged = env->GetMethodID(clazz, "onChannelStateChanged",
- "(I[BIIILjava/io/FileDescriptor;)V");
-
-/*
- if ( (btInf = getBluetoothInterface()) == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if ( (sBluetoothHdpInterface = (bthl_interface_t *)
- btInf->get_profile_interface(BT_PROFILE_HEALTH_ID)) == NULL) {
- ALOGE("Failed to get Bluetooth Handsfree Interface");
- return;
- }
-
- // TODO(BT) do this only once or
- // Do we need to do this every time the BT reenables?
- if ( (status = sBluetoothHdpInterface->init(&sBluetoothHdpCallbacks)) != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth HDP, status: %d", status);
- sBluetoothHdpInterface = NULL;
- return;
- }
-*/
-
+ "(I[BIIILjava/io/FileDescriptor;)V");
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -168,7 +142,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -195,7 +168,10 @@
bthl_reg_param_t reg_param;
int app_id;
- if (!sBluetoothHdpInterface) return NULL;
+ if (!sBluetoothHdpInterface) {
+ ALOGE("Failed to register health app. No Bluetooth Health Interface available");
+ return -1;
+ }
mdep_cfg.mdep_role = (bthl_mdep_role_t) role;
mdep_cfg.data_type = data_type;
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index d9f7c78..1666990 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -399,12 +399,6 @@
};
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
- /*
- const bt_interface_t* btInf;
- bt_status_t status;
- */
-
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
method_onAudioStateChanged = env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
@@ -424,27 +418,6 @@
method_onUnknownAt = env->GetMethodID(clazz, "onUnknownAt", "(Ljava/lang/String;[B)V");
method_onKeyPressed = env->GetMethodID(clazz, "onKeyPressed", "([B)V");
- /*
- if ( (btInf = getBluetoothInterface()) == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if ( (sBluetoothHfpInterface = (bthf_interface_t *)
- btInf->get_profile_interface(BT_PROFILE_HANDSFREE_ID)) == NULL) {
- ALOGE("Failed to get Bluetooth Handsfree Interface");
- return;
- }
-
- // TODO(BT) do this only once or
- // Do we need to do this every time the BT reenables?
- if ( (status = sBluetoothHfpInterface->init(&sBluetoothHfpCallbacks)) != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth HFP, status: %d", status);
- sBluetoothHfpInterface = NULL;
- return;
- }
- */
-
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -487,7 +460,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
diff --git a/jni/com_android_bluetooth_hfpclient.cpp b/jni/com_android_bluetooth_hfpclient.cpp
index 62133eb..333035c 100644
--- a/jni/com_android_bluetooth_hfpclient.cpp
+++ b/jni/com_android_bluetooth_hfpclient.cpp
@@ -344,7 +344,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
diff --git a/jni/com_android_bluetooth_hid.cpp b/jni/com_android_bluetooth_hid.cpp
index adff0f5..1630a5d 100644
--- a/jni/com_android_bluetooth_hid.cpp
+++ b/jni/com_android_bluetooth_hid.cpp
@@ -196,37 +196,12 @@
// Define native functions
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
-// const bt_interface_t* btInf;
-// bt_status_t status;
-
method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged", "([BI)V");
method_onGetProtocolMode = env->GetMethodID(clazz, "onGetProtocolMode", "([BI)V");
method_onGetReport = env->GetMethodID(clazz, "onGetReport", "([B[BI)V");
method_onHandshake = env->GetMethodID(clazz, "onHandshake", "([BI)V");
method_onVirtualUnplug = env->GetMethodID(clazz, "onVirtualUnplug", "([BI)V");
-/*
- if ( (btInf = getBluetoothInterface()) == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if ( (sBluetoothHidInterface = (bthh_interface_t *)
- btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID)) == NULL) {
- ALOGE("Failed to get Bluetooth Handsfree Interface");
- return;
- }
-
- // TODO(BT) do this only once or
- // Do we need to do this every time the BT reenables?
- if ( (status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks)) != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth HID, status: %d", status);
- sBluetoothHidInterface = NULL;
- return;
- }
-
-*/
ALOGI("%s: succeeds", __FUNCTION__);
}
@@ -271,7 +246,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
diff --git a/jni/com_android_bluetooth_pan.cpp b/jni/com_android_bluetooth_pan.cpp
index d2b2f6e..30056e9 100644
--- a/jni/com_android_bluetooth_pan.cpp
+++ b/jni/com_android_bluetooth_pan.cpp
@@ -94,9 +94,6 @@
// Define native functions
static void classInitNative(JNIEnv* env, jclass clazz) {
- int err;
- bt_status_t status;
-
method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged",
"([BIIII)V");
method_onControlStateChanged = env->GetMethodID(clazz, "onControlStateChanged",
@@ -150,7 +147,6 @@
}
static void cleanupNative(JNIEnv *env, jobject object) {
- bt_status_t status;
if (!btIf) return;
if (sPanIf !=NULL) {
@@ -170,7 +166,6 @@
static jboolean enablePanNative(JNIEnv *env, jobject object, jint local_role) {
bt_status_t status = BT_STATUS_FAIL;
debug("in");
- jbyte *addr;
if (sPanIf)
status = sPanIf->enable(local_role);
debug("out");
@@ -179,7 +174,6 @@
static jint getPanLocalRoleNative(JNIEnv *env, jobject object) {
debug("in");
int local_role = 0;
- jbyte *addr;
if (sPanIf)
local_role = sPanIf->get_local_role();
debug("out");
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index beaf3bf..889ab7b 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -33,8 +33,6 @@
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
static const uint8_t UUID_MAP_MNS[] = {0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
-static const uint8_t UUID_SPP[] = {0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00,
- 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
static const uint8_t UUID_SAP[] = {0x00, 0x00, 0x11, 0x2D, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
// TODO:
@@ -81,7 +79,6 @@
static void initializeNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -151,7 +148,7 @@
ALOGE("failed to get uuid");
goto Fail;
}
- ALOGD("%s UUID %.*X",__FUNCTION__,16, (uint8_t*)uuid);
+ ALOGD("%s UUID %.*s",__FUNCTION__,16, (uint8_t*)uuid);
if ((ret = sBluetoothSdpInterface->sdp_search((bt_bdaddr_t *)addr,
@@ -537,7 +534,6 @@
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
- bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -551,7 +547,7 @@
}
if (sCallbacksObj != NULL) {
- ALOGW("Cleaning up Bluetooth Health object");
+ ALOGW("Cleaning up Bluetooth SDP object");
env->DeleteGlobalRef(sCallbacksObj);
sCallbacksObj = NULL;
}
diff --git a/res/drawable-hdpi/bt_incomming_file_notification.png b/res/drawable-hdpi/bt_incomming_file_notification.png
deleted file mode 100644
index ff72837..0000000
--- a/res/drawable-hdpi/bt_incomming_file_notification.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/bt_incomming_file_notification.png b/res/drawable-mdpi/bt_incomming_file_notification.png
deleted file mode 100644
index d53e0eb..0000000
--- a/res/drawable-mdpi/bt_incomming_file_notification.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/bt_incomming_file_notification.png b/res/drawable-xhdpi/bt_incomming_file_notification.png
deleted file mode 100644
index 3762470..0000000
--- a/res/drawable-xhdpi/bt_incomming_file_notification.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/bt_incomming_file_notification.xml b/res/drawable/bt_incomming_file_notification.xml
new file mode 100644
index 0000000..ed45b3b
--- /dev/null
+++ b/res/drawable/bt_incomming_file_notification.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M13.5 8.1l-6.2-6.1v8.9l-3.8-3.5-.9 1 4 3.6-3.9 3.7.9 1 3.8-3.5v8.8l6.2-6.1-3.9-4.3 3.8-3.5zm-4.5-2.6l2.6 2.6-2.6 2.2v-4.8zm2.6 10.4l-2.6 2.6v-5.5l2.6 2.9zm5.6-7.5h-1.7l2.7-3.6 2.7 3.6h-1.7v2.7h-2v-2.7zm0 4.6h2v2.7h1.7l-2.7 3.6-2.7-3.6h1.7v-2.7z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/res/drawable/ic_accept.xml b/res/drawable/ic_accept.xml
new file mode 100644
index 0000000..5ed8b4e
--- /dev/null
+++ b/res/drawable/ic_accept.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/res/drawable/ic_decline.xml b/res/drawable/ic_decline.xml
new file mode 100644
index 0000000..6c7d83a
--- /dev/null
+++ b/res/drawable/ic_decline.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 41953a1..85a9942 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aanvaar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Daar was \'n uittelling terwyl \'n inkomende lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" aanvaar is"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-deel: Inkomende lêer"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Wil jy hierdie lêer ontvang?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Inkomende lêer vanaf \'n ander toestel. Bevestig dat jy hierdie lêer wil ontvang."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkomende lêer"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is gereed om <xliff:g id="FILE">%2$s</xliff:g> te stuur"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Ontvang <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: Het \"<xliff:g id="FILE">%1$s</xliff:g>\" ontvang"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Lêer <xliff:g id="FILE">%1$s</xliff:g> nie ontvang nie"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vee uit"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Stoor"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Kanselleer"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Kies die rekeninge wat jy deur Bluetooth wil deel. Jy moet steeds enige toegang tot die rekeninge aanvaar wanneer jy koppel."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Kies die rekeninge wat jy deur Bluetooth wil deel. Jy moet steeds enige toegang tot die rekeninge aanvaar wanneer jy koppel."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Gleuwe oor:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Programikoon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth-boodskapdeelinstellings"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Kan nie rekening kies nie. 0 gleuwe oor"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-oudio is gekoppel"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-oudio is ontkoppel"</string>
</resources>
diff --git a/res/values-af/strings_pbap_client.xml b/res/values-af/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-af/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index bae6be9..1f5c9c4 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ተቀበል"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"እሺ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"ከ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ገቢ መልዕክት ፋይል እየተቀበለ ሳለ ጊዜ አልቆ ነበር።"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"የብሉቱዝ መጋሪያ፡ ገቢ ፋይል"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ይህን ፋይል መቀበል ይፈልጋሉ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ከሌላ መሣሪያ የሚገባ ፋይል አለ፣ ይህን ፋይል ለመቀበል መፈለግዎን ያረጋግጡ።"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ገቢ ፋይል"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g>ን ለመላክ ዝግጁ ነው"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> እየተቀበለ"</string>
<string name="notification_received" msgid="3324588019186687985">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> ደርሷል"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ብሉቱዝ ማጋሪያ፡ ፋይል<xliff:g id="FILE">%1$s</xliff:g> አልደረሰም"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"አጽዳ"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"አስቀምጥ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ይቅር"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"በብሉቱዝ በኩል ማጋራት የሚፈልጓቸውን መለያዎች ይምረጡ። አሁንም በሚገናኙበት ወቅት ማንኛቸውም የመለያዎቹ መዳረሻ መፍቀድ አለብዎት።"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"በብሉቱዝ በኩል ማጋራት የሚፈልጓቸውን መለያዎች ይምረጡ። አሁንም በሚገናኙበት ወቅት ማንኛቸውም የመለያዎቹ መዳረሻ መፍቀድ አለብዎት።"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"የቀሩ መክተቻዎች፦"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"የመተግበሪያ አዶ"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"የብሉቱዝ የመልዕክት ማጋሪያ ቅንብሮች"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"መለያን መምረጥ አይቻልም። 0 መክተቻዎች ይቀራሉ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"የብሉቱዝ ኦዲዮ ተገናኝቷል"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"የብሉቱዝ ኦዲዮ ግንኙነት ተቋርጧል"</string>
</resources>
diff --git a/res/values-am/strings_pbap_client.xml b/res/values-am/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-am/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index ecb7c95..6d78bc6 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"قبول"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"موافق"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"انتهت المهلة أثناء قبول ملف وراد من \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"مشاركة البلوتوث: ملف وارد"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"هل تريد تلقي هذا الملف؟"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"هناك ملف وارد من جهاز آخر. عليك تأكيد أنك تريد استلام هذا الملف."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ملف وارد"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> جاهز لإرسال <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"مشاركة البلوتوث: يتم استلام <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"مشاركة البلوتوث: تم استلام <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"مشاركة البلوتوث: لم يتم استلام الملف <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -132,9 +131,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"محو"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"حفظ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"إلغاء"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"حدد الحسابات التي تريد مشاركتها عبر بلوتوث. لا يزال يتعين عليك قبول أي دخول إلى الحسابات أثناء الاتصال."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"حدد الحسابات التي تريد مشاركتها عبر البلوتوث. لا يزال يتعين عليك قبول أي دخول إلى الحسابات أثناء الاتصال."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"المنافذ المتبقية:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"رمز التطبيق"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"إعدادات مشاركة الرسائل عبر بلوتوث"</string>
- <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"يتعذر تحديد الحساب. عدد المنافذ المتبقية: 0"</string>
+ <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"يتعذر تحديد الحساب. عدد المنافذ المتبقية: ۰"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"تم الاتصال بالبث الصوتي عبر البلوتوث."</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"انقطع الاتصال بالبث الصوتي عبر البلوتوث."</string>
</resources>
diff --git a/res/values-ar/strings_pbap_client.xml b/res/values-ar/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ar/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 2f9d291..facad2f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Qəbul edirəm"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" adlı istifadəçidən gələn faylı qəbul edərkən gecikmə baş verdi"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth paylaşım: Gələn fayl"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Bu faylı qəbul etmək istəyirsiniz?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Başqa cihazdan fayl gəlir. Qəbul etmə istəyinizi təsdiq edin."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gələn fayl"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> faylını göndərməyə hazırdır"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> qəbul edilir"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> qəbul edildi"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> faylı qəbul edilmədi"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Silin"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Yadda saxlayın"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ləğv edin"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth vasitəsilə bölüşmək istəyirəm hesabı seçin. Siz hələ birləşdirən zaman hesablarına hər hansı bir acceess qəbul etmək lazımdır."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth vasitəsilə paylaşmaq istədiyiniz hesabları seçin. Qoşulma zamanı hesablara olan istənilən girişi qəbul etməlisiniz."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Qalmış slotlar:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Tətbiq ikonası"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth Mesaj Paylaşma Ayarları"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Hesab seçmək olmur. 0 slot qalıb"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio bağlantısı yaradıldı"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio ilə bağlantı kəsildi"</string>
</resources>
diff --git a/res/values-az-rAZ/strings_pbap_client.xml b/res/values-az-rAZ/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-az-rAZ/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..c3b1d1a
--- /dev/null
+++ b/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pristup menadžeru preuzimanja."</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Omogućava aplikaciji da pristupa menadžeru za deljenje preko Bluetooth-a i da ga koristi za prenos datoteka."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Pristup Bluetooth uređaja sa bele liste."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na belu listu, što omogućava tom uređaju da šalje datoteke ovom uređaju bez potvrde korisnika."</string>
+ <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
+ <string name="unknown_device" msgid="9221903979877041009">"Nepoznati uređaj"</string>
+ <string name="unknownNumber" msgid="4994750948072751566">"Nepoznato"</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"Režim rada u avionu"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Ne možete da koristite Bluetooth u režimu rada u avionu."</string>
+ <string name="bt_enable_title" msgid="8657832550503456572"></string>
+ <string name="bt_enable_line1" msgid="7203551583048149">"Da biste mogli da koristite Bluetooth usluge, najpre morate da uključite Bluetooth."</string>
+ <string name="bt_enable_line2" msgid="4341936569415937994">"Želite li odmah da uključite Bluetooth?"</string>
+ <string name="bt_enable_cancel" msgid="1988832367505151727">"Otkaži"</string>
+ <string name="bt_enable_ok" msgid="3432462749994538265">"Uključi"</string>
+ <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Prenos datoteke"</string>
+ <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Želite li da prihvatite dolaznu datoteku?"</string>
+ <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Odbij"</string>
+ <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Prihvati"</string>
+ <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Potvrdi"</string>
+ <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Došlo je do vremenskog ograničenja tokom prijema dolazne datoteke od „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazna datoteka"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> je spreman/na da pošalje <xliff:g id="FILE">%2$s</xliff:g>"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth deljenje: prijem <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received" msgid="3324588019186687985">"Bluetooth deljenje: primljeno <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth deljenje: datoteka <xliff:g id="FILE">%1$s</xliff:g> nije primljena"</string>
+ <string name="notification_sending" msgid="3035748958534983833">"Bluetooth deljenje: slanje <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent" msgid="9218710861333027778">"Bluetooth deljenje: poslato <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent_complete" msgid="302943281067557969">"Dovršeno je 100%"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"Bluetooth deljenje: datoteka <xliff:g id="FILE">%1$s</xliff:g> nije poslata"</string>
+ <string name="download_title" msgid="3353228219772092586">"Prenos datoteke"</string>
+ <string name="download_line1" msgid="4926604799202134144">"Od: „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="download_line2" msgid="5876973543019417712">"Datoteka: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_line3" msgid="4384821622908676061">"Veličina datoteke: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="download_line4" msgid="8535996869722666525"></string>
+ <string name="download_line5" msgid="3069560415845295386">"Primanje datoteke..."</string>
+ <string name="download_cancel" msgid="9177305996747500768">"Zaustavi"</string>
+ <string name="download_ok" msgid="5000360731674466039">"Sakrij"</string>
+ <string name="incoming_line1" msgid="2127419875681087545">"Od"</string>
+ <string name="incoming_line2" msgid="3348994249285315873">"Naziv datoteke"</string>
+ <string name="incoming_line3" msgid="7954237069667474024">"Veličina"</string>
+ <string name="download_fail_line1" msgid="3846450148862894552">"Datoteka nije primljena"</string>
+ <string name="download_fail_line2" msgid="8950394574689971071">"Datoteka: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_fail_line3" msgid="3451040656154861722">"Razlog: <xliff:g id="REASON">%1$s</xliff:g>"</string>
+ <string name="download_fail_ok" msgid="1521733664438320300">"Potvrdi"</string>
+ <string name="download_succ_line5" msgid="4509944688281573595">"Datoteka je primljena"</string>
+ <string name="download_succ_ok" msgid="7053688246357050216">"Otvori"</string>
+ <string name="upload_line1" msgid="2055952074059709052">"Kome: „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
+ <string name="upload_line3" msgid="4920689672457037437">"Tip datoteke: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
+ <string name="upload_line5" msgid="7759322537674229752">"Slanje datoteke..."</string>
+ <string name="upload_succ_line5" msgid="5687317197463383601">"Datoteka je poslata"</string>
+ <string name="upload_succ_ok" msgid="7705428476405478828">"Potvrdi"</string>
+ <string name="upload_fail_line1" msgid="7899394672421491701">"Datoteka nije poslata na <xliff:g id="RECIPIENT">%1$s</xliff:g>."</string>
+ <string name="upload_fail_line1_2" msgid="2108129204050841798">"Datoteka: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="upload_fail_ok" msgid="5807702461606714296">"Pokušaj ponovo"</string>
+ <string name="upload_fail_cancel" msgid="9118496285835687125">"Zatvori"</string>
+ <string name="bt_error_btn_ok" msgid="5965151173011534240">"Potvrdi"</string>
+ <string name="unknown_file" msgid="6092727753965095366">"Nepoznata datoteka"</string>
+ <string name="unknown_file_desc" msgid="480434281415453287">"Nema aplikacija za obradu ovog tipa datoteke. \n"</string>
+ <string name="not_exist_file" msgid="3489434189599716133">"Nema datoteke"</string>
+ <string name="not_exist_file_desc" msgid="4059531573790529229">"Datoteka ne postoji. \n"</string>
+ <string name="enabling_progress_title" msgid="436157952334723406">"Sačekajte…"</string>
+ <string name="enabling_progress_content" msgid="4601542238119927904">"Uključivanje Bluetooth-a…"</string>
+ <string name="bt_toast_1" msgid="972182708034353383">"Datoteka će biti primljena. Proveravajte tok na tabli sa obaveštenjima."</string>
+ <string name="bt_toast_2" msgid="8602553334099066582">"Nije moguće primiti datoteku."</string>
+ <string name="bt_toast_3" msgid="6707884165086862518">"Zaustavljen prijem datoteke od pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_toast_4" msgid="4678812947604395649">"Slanje datoteke primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
+ <string name="bt_toast_5" msgid="2846870992823019494">"Slanje<xliff:g id="NUMBER">%1$s</xliff:g> datoteka primaocu „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
+ <string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nema dovoljno prostora u USB memoriji da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nema dovoljno prostora na SD kartici da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_2" msgid="2965243265852680543">"Potreban prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Previše zahteva se obrađuje. Pokušajte ponovo kasnije."</string>
+ <string name="status_pending" msgid="2503691772030877944">"Prenos datoteke još nije počeo."</string>
+ <string name="status_running" msgid="6562808920311008696">"Prenos datoteke je u toku."</string>
+ <string name="status_success" msgid="239573225847565868">"Prenos datoteke je dovršen."</string>
+ <string name="status_not_accept" msgid="1695082417193780738">"Sadržaj nije podržan."</string>
+ <string name="status_forbidden" msgid="613956401054050725">"Ciljni uređaj je zabranio prenos."</string>
+ <string name="status_canceled" msgid="6664490318773098285">"Korisnik je otkazao prenos."</string>
+ <string name="status_file_error" msgid="3671917770630165299">"Problem sa skladištem."</string>
+ <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nema USB memorije."</string>
+ <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nema SD kartice. Umetnite SD karticu da biste sačuvali prenete datoteke."</string>
+ <string name="status_connection_error" msgid="947681831523219891">"Povezivanje nije uspelo."</string>
+ <string name="status_protocol_error" msgid="3245444473429269539">"Nije moguće ispravno obraditi zahtev."</string>
+ <string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata greška."</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno preko Bluetooth-a"</string>
+ <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Primljeno u celosti."</string>
+ <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Slanje je dovršeno."</string>
+ <string name="inbound_history_title" msgid="6940914942271327563">"Dolazni prenosi"</string>
+ <string name="outbound_history_title" msgid="4279418703178140526">"Odlazni prenosi"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"Istorija prenosa je prazna."</string>
+ <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Sve stavke će biti izbrisane sa liste."</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"Deljenje preko Bluetooth-a: poslate datoteke"</string>
+ <string name="inbound_noti_title" msgid="4143352641953027595">"Deljenje preko Bluetooth-a: primljene datoteke"</string>
+ <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
+ <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspešna.</item>
+ <item quantity="few"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspešne.</item>
+ <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspešnih.</item>
+ </plurals>
+ <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
+ <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspešna, %2$s</item>
+ <item quantity="few"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspešne, %2$s</item>
+ <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspešnih, %2$s</item>
+ </plurals>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"Obriši listu"</string>
+ <string name="transfer_menu_open" msgid="3368984869083107200">"Otvori"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"Obriši sa liste"</string>
+ <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Brisanje"</string>
+ <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Sačuvaj"</string>
+ <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Otkaži"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Izaberite naloge koje želite da delite preko Bluetooth-a. I dalje morate da prihvatite bilo kakav pristup nalozima pri povezivanju."</string>
+ <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Preostalih mesta:"</string>
+ <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikacije"</string>
+ <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Podešavanja Bluetooth deljenja poruka"</string>
+ <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Nije moguće izabrati nalog. Nema preostalih mesta"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio je povezan"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza sa Bluetooth audijom je prekinuta"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings_pbap.xml b/res/values-b+sr+Latn/strings_pbap.xml
new file mode 100644
index 0000000..4896295
--- /dev/null
+++ b/res/values-b+sr+Latn/strings_pbap.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Unesite ključ sesije za %1$s"</string>
+ <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Potreban je ključ sesije za Bluetooth"</string>
+ <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"Isteklo je vreme za prihvatanje veze sa uređajem %1$s"</string>
+ <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"Isteklo je vreme za unos ključa sesije pomoću %1$s"</string>
+ <string name="auth_notif_ticker" msgid="1575825798053163744">"Zahtev za potvrdu identiteta preko Obex protokola"</string>
+ <string name="auth_notif_title" msgid="7599854855681573258">"Ključ sesije"</string>
+ <string name="auth_notif_message" msgid="6667218116427605038">"Unesite ključ sesije za %1$s"</string>
+ <string name="defaultname" msgid="4821590500649090078">"Oprema za automobil"</string>
+ <string name="unknownName" msgid="2841414754740600042">"Nepoznato ime"</string>
+ <string name="localPhoneName" msgid="2349001318925409159">"Moje ime"</string>
+ <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings_pbap_client.xml b/res/values-b+sr+Latn/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-b+sr+Latn/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings_sap.xml b/res/values-b+sr+Latn/strings_sap.xml
new file mode 100644
index 0000000..7e36fb9
--- /dev/null
+++ b/res/values-b+sr+Latn/strings_sap.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"Pristup SIM kartici preko Bluetooth-a"</string>
+ <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"Pristup SIM kartici preko Bluetooth-a"</string>
+ <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"Želite li da pošaljete klijentu zahtev za prekid veze?"</string>
+ <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"Čeka se da klijent prekine vezu"</string>
+ <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"Prekini vezu"</string>
+ <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"Prinudno prekini vezu"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/test_strings.xml b/res/values-b+sr+Latn/test_strings.xml
new file mode 100644
index 0000000..6e728d8
--- /dev/null
+++ b/res/values-b+sr+Latn/test_strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="hello" msgid="1740533743008967039">"Zdravo svima, proba"</string>
+ <string name="app_name" msgid="1203877025577761792">"Deljenje preko Bluetooth-a"</string>
+ <string name="insert_record" msgid="1450997173838378132">"Umetni zapis"</string>
+ <string name="update_record" msgid="2480425402384910635">"Potvrdi zapis"</string>
+ <string name="ack_record" msgid="6716152390978472184">"Ack zapis"</string>
+ <string name="deleteAll_record" msgid="4383349788485210582">"Izbriši sve zapise"</string>
+ <string name="ok_button" msgid="6519033415223065454">"Potvrdi"</string>
+ <string name="delete_record" msgid="4645040331967533724">"Izbriši zapis"</string>
+ <string name="start_server" msgid="9034821924409165795">"Pokreni TCP server"</string>
+ <string name="notify_server" msgid="4369106744022969655">"Obavesti TCP server"</string>
+</resources>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..09d516a
--- /dev/null
+++ b/res/values-be-rBY/strings.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Доступ да менеджэра спампоўванняў."</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Дазваляе прыкладанням атрымліваць доступ да менеджэра BluetoothShare і выкарыстоўваць яго для перадачы файлаў."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Даданне прылад Bluetooth у белы спiс."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Дазваляе прыкладанням часова ўносiць у белы спіс прылады Bluetooth, дазваляючы iм адпраўляць файлы на гэту прыладу без пацверджання карыстальнiка."</string>
+ <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
+ <string name="unknown_device" msgid="9221903979877041009">"Невядомая прылада"</string>
+ <string name="unknownNumber" msgid="4994750948072751566">"Невядомы"</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"Рэжым палёту"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Вы не можаце выкарыстоўваць Bluetooth у рэжыме палёту."</string>
+ <string name="bt_enable_title" msgid="8657832550503456572"></string>
+ <string name="bt_enable_line1" msgid="7203551583048149">"Каб выкарыстоўваць перадачу праз Bluetooth, спачатку неабходна ўключыць Bluetooth."</string>
+ <string name="bt_enable_line2" msgid="4341936569415937994">"Уключыць Bluetooth зараз?"</string>
+ <string name="bt_enable_cancel" msgid="1988832367505151727">"Адмяніць"</string>
+ <string name="bt_enable_ok" msgid="3432462749994538265">"Уключыць"</string>
+ <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Перадача файлаў"</string>
+ <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Прыняць уваходны файл?"</string>
+ <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Адхіліць"</string>
+ <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Прыняць"</string>
+ <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ОК"</string>
+ <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Тайм-аўт прыняцця ўваходнага файла ад адпраўніка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Уваходны файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> гатовы(-ая) адправіць <xliff:g id="FILE">%2$s</xliff:g>"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"Перадача праз Bluetooth: атрыманне файла <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received" msgid="3324588019186687985">"Перадача праз Bluetooth: атрыманы файл <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"Перадача прз Bluetooth: файл <xliff:g id="FILE">%1$s</xliff:g> не атрыманы"</string>
+ <string name="notification_sending" msgid="3035748958534983833">"Перадача праз Bluetooth: адпраўка файла <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent" msgid="9218710861333027778">"Перадача праз Bluetooth: адпраўлены файл <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent_complete" msgid="302943281067557969">"Завершана 100%"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"Перадача праз Bluetooth: файл <xliff:g id="FILE">%1$s</xliff:g> не адпраўлены"</string>
+ <string name="download_title" msgid="3353228219772092586">"Перадача файлаў"</string>
+ <string name="download_line1" msgid="4926604799202134144">"Ад: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="download_line2" msgid="5876973543019417712">"Файл: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_line3" msgid="4384821622908676061">"Памер файла: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="download_line4" msgid="8535996869722666525"></string>
+ <string name="download_line5" msgid="3069560415845295386">"Атрыманне файла..."</string>
+ <string name="download_cancel" msgid="9177305996747500768">"Спыніць"</string>
+ <string name="download_ok" msgid="5000360731674466039">"Схаваць"</string>
+ <string name="incoming_line1" msgid="2127419875681087545">"Ад каго:"</string>
+ <string name="incoming_line2" msgid="3348994249285315873">"Назва файла"</string>
+ <string name="incoming_line3" msgid="7954237069667474024">"Памер"</string>
+ <string name="download_fail_line1" msgid="3846450148862894552">"Файл не атрыманы"</string>
+ <string name="download_fail_line2" msgid="8950394574689971071">"Файл: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_fail_line3" msgid="3451040656154861722">"Прычына: <xliff:g id="REASON">%1$s</xliff:g>"</string>
+ <string name="download_fail_ok" msgid="1521733664438320300">"ОК"</string>
+ <string name="download_succ_line5" msgid="4509944688281573595">"Файл атрыманы"</string>
+ <string name="download_succ_ok" msgid="7053688246357050216">"Адкрыць"</string>
+ <string name="upload_line1" msgid="2055952074059709052">"Каму: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+ <string name="upload_line3" msgid="4920689672457037437">"Тып файла: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
+ <string name="upload_line5" msgid="7759322537674229752">"Адпраўка файла..."</string>
+ <string name="upload_succ_line5" msgid="5687317197463383601">"Файл адпраўлены"</string>
+ <string name="upload_succ_ok" msgid="7705428476405478828">"ОК"</string>
+ <string name="upload_fail_line1" msgid="7899394672421491701">"Файл не адпраўлены атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"."</string>
+ <string name="upload_fail_line1_2" msgid="2108129204050841798">"Файл: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="upload_fail_ok" msgid="5807702461606714296">"Паспрабаваць яшчэ раз"</string>
+ <string name="upload_fail_cancel" msgid="9118496285835687125">"Закрыць"</string>
+ <string name="bt_error_btn_ok" msgid="5965151173011534240">"ОК"</string>
+ <string name="unknown_file" msgid="6092727753965095366">"Невядомы файл"</string>
+ <string name="unknown_file_desc" msgid="480434281415453287">"Няма прыкладанняў для апрацоўкі файлаў гэтага тыпу. \n"</string>
+ <string name="not_exist_file" msgid="3489434189599716133">"няма файла"</string>
+ <string name="not_exist_file_desc" msgid="4059531573790529229">"Файл не існуе. \n"</string>
+ <string name="enabling_progress_title" msgid="436157952334723406">"Чакайце..."</string>
+ <string name="enabling_progress_content" msgid="4601542238119927904">"Уключэнне Bluetooth..."</string>
+ <string name="bt_toast_1" msgid="972182708034353383">"Файл будзе атрыманы. Сачыце за прагрэсам на панэлі апавяшчэнняў."</string>
+ <string name="bt_toast_2" msgid="8602553334099066582">"Немагчыма атрымаць файл."</string>
+ <string name="bt_toast_3" msgid="6707884165086862518">"Атрыманне файла ад адпраўніка \"<xliff:g id="SENDER">%1$s</xliff:g>\" спыненае"</string>
+ <string name="bt_toast_4" msgid="4678812947604395649">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+ <string name="bt_toast_5" msgid="2846870992823019494">"Адпраўка файлаў (<xliff:g id="NUMBER">%1$s</xliff:g>) атрымальніку \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
+ <string name="bt_toast_6" msgid="1855266596936622458">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" спыненая"</string>
+ <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Не хапае месца на USB-назапашвальнiку, каб захаваць файл ад адпраўнiка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"На SD-карце недастаткова месца, каб захаваць файл ад адпраўніка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_2" msgid="2965243265852680543">"Спатрэбіцца месца: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Апрацоўваецца занадта шмат запытаў. Паспрабуйце пазней."</string>
+ <string name="status_pending" msgid="2503691772030877944">"Перадача файла яшчэ не пачалася"</string>
+ <string name="status_running" msgid="6562808920311008696">"Ідзе перадача файлаў."</string>
+ <string name="status_success" msgid="239573225847565868">"Перадача файлаў паспяхова завершана."</string>
+ <string name="status_not_accept" msgid="1695082417193780738">"Змесціва не падтрымліваецца."</string>
+ <string name="status_forbidden" msgid="613956401054050725">"Перадача забаронена мэтавай прыладай."</string>
+ <string name="status_canceled" msgid="6664490318773098285">"Перадача адменена карыстальнiкам."</string>
+ <string name="status_file_error" msgid="3671917770630165299">"Праблема ўнутранага сховiшча"</string>
+ <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Няма USB-назапашвальнiка."</string>
+ <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Няма SD-карты. Устаўце яе, каб захаваць перададзеныя файлы."</string>
+ <string name="status_connection_error" msgid="947681831523219891">"Няўдалая спроба падключэння."</string>
+ <string name="status_protocol_error" msgid="3245444473429269539">"Запыт не можа быць правільна апрацаваны"</string>
+ <string name="status_unknown_error" msgid="8156660554237824912">"Невядомая памылка."</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Атрыманае праз Bluetooth"</string>
+ <string name="download_success" msgid="7036160438766730871">"Атрыманне завершанае: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
+ <string name="upload_success" msgid="4014469387779648949">"Адпраўленне завершана: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
+ <string name="inbound_history_title" msgid="6940914942271327563">"Уваходныя перадачы"</string>
+ <string name="outbound_history_title" msgid="4279418703178140526">"Выходныя перадачы"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"Гісторыя перадач пустая."</string>
+ <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Усе элементы будуць выдаленыя са спісу."</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"Перадача праз Bluetooth: адпраўленыя файлы"</string>
+ <string name="inbound_noti_title" msgid="4143352641953027595">"Перадача праз Bluetooth: атрыманыя файлы"</string>
+ <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
+ <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> беспаспяховы.</item>
+ <item quantity="few"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> беспаспяховыя.</item>
+ <item quantity="many"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> беспаспяховых.</item>
+ <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> беспаспяховага.</item>
+ </plurals>
+ <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
+ <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> паспяховы, %2$s</item>
+ <item quantity="few"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> паспяховыя, %2$s</item>
+ <item quantity="many"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> паспяховых, %2$s</item>
+ <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> паспяховага, %2$s</item>
+ </plurals>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"Ачысціць спіс"</string>
+ <string name="transfer_menu_open" msgid="3368984869083107200">"Адкрыць"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"Выдаліць са спісу"</string>
+ <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ачысціць"</string>
+ <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Захаваць"</string>
+ <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Скасаваць"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Выберыце ўліковыя запісы, якія вы хочаце абагульваць па Bluetooth. Тым не менш, вам давядзецца асобна даваць кожны доступ пры падлучэнні."</string>
+ <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Засталося слотаў:"</string>
+ <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Значок праграмы"</string>
+ <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Налады агульнага доступу да паведамленняў па Bluetooth"</string>
+ <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Немагчыма выбраць уліковы запіс. Засталося 0 слотаў"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-аўдыя падключана"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-аўдыя адключана"</string>
+</resources>
diff --git a/res/values-be-rBY/strings_pbap.xml b/res/values-be-rBY/strings_pbap.xml
new file mode 100644
index 0000000..d80bee3
--- /dev/null
+++ b/res/values-be-rBY/strings_pbap.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Увядзіце ключ сеансу для %1$s"</string>
+ <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Патрэбны ключ для сеанса перадачы праз Bluetooth"</string>
+ <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"Тайм-аўт прыёму злучэння з %1$s"</string>
+ <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"Таўм-аўт уводу ключа сеансу з %1$s"</string>
+ <string name="auth_notif_ticker" msgid="1575825798053163744">"Запыт на аўтэнтыфікацыю Obex"</string>
+ <string name="auth_notif_title" msgid="7599854855681573258">"Ключ сесіі"</string>
+ <string name="auth_notif_message" msgid="6667218116427605038">"Увядзіце ключ сеансу для %1$s"</string>
+ <string name="defaultname" msgid="4821590500649090078">"Carkit"</string>
+ <string name="unknownName" msgid="2841414754740600042">"Невядомае імя"</string>
+ <string name="localPhoneName" msgid="2349001318925409159">"Маё імя"</string>
+ <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
+</resources>
diff --git a/res/values-be-rBY/strings_pbap_client.xml b/res/values-be-rBY/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-be-rBY/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-be-rBY/strings_sap.xml b/res/values-be-rBY/strings_sap.xml
new file mode 100644
index 0000000..6ac6869
--- /dev/null
+++ b/res/values-be-rBY/strings_sap.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"Доступ да SIM па Bluetooth"</string>
+ <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"Доступ да SIM па Bluetooth"</string>
+ <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"Запытаць адлучэнне кліента?"</string>
+ <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"Чаканне адлучэння кліента"</string>
+ <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"Адлучыць"</string>
+ <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"Прымусовае адлучэнне"</string>
+</resources>
diff --git a/res/values-be-rBY/test_strings.xml b/res/values-be-rBY/test_strings.xml
new file mode 100644
index 0000000..2307f8e
--- /dev/null
+++ b/res/values-be-rBY/test_strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="hello" msgid="1740533743008967039">"Hello World, TestActivity"</string>
+ <string name="app_name" msgid="1203877025577761792">"Перадача праз Bluetooth"</string>
+ <string name="insert_record" msgid="1450997173838378132">"Уставіць запіс"</string>
+ <string name="update_record" msgid="2480425402384910635">"Пацвердзіць запіс"</string>
+ <string name="ack_record" msgid="6716152390978472184">"Запіс"</string>
+ <string name="deleteAll_record" msgid="4383349788485210582">"Выдаліць усе запісы"</string>
+ <string name="ok_button" msgid="6519033415223065454">"ОК"</string>
+ <string name="delete_record" msgid="4645040331967533724">"Выдаліць запіс"</string>
+ <string name="start_server" msgid="9034821924409165795">"Запусціць сервер TCP"</string>
+ <string name="notify_server" msgid="4369106744022969655">"Апавясцiць сервер TCP"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index c55c49a..4af7439 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Приемане"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Времето за изчакване изтече при приемането на входящ файл от „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Споделяне чрез Bluetooth: Вх. файл"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Искате ли да получите този файл?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Входящ файл от друго устройство. Потвърдете, че искате да го получите."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Входящ файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> има готовност да изпрати „<xliff:g id="FILE">%2$s</xliff:g>“"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Споделяне чрез Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> се получава"</string>
<string name="notification_received" msgid="3324588019186687985">"Споделяне чрез Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> се получи"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Споделяне чрез Bluetooth: Файлът <xliff:g id="FILE">%1$s</xliff:g> не е получен"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Изчистване"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Запазване"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Отказ"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Изберете профилите, които искате да споделите през Bluetooth. Пак трябва да приемете достъпа до тях при свързване."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Изберете профилите, които искате да споделите през Bluetooth. Пак трябва да приемете достъпа до тях при свързване."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Оставащи слотове:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Икона на приложението"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Настройки за споделяне на съобщения през Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Не може да се избере профил. Остават 0 слота"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Установена е аудиовръзка през Bluetooth"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиовръзката през Bluetooth е прекратена"</string>
</resources>
diff --git a/res/values-bg/strings_pbap_client.xml b/res/values-bg/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-bg/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 58216fa..4a21cc8 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"স্বীকার করুন"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ঠিক আছে"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" এর থেকে ইনকামিং ফাইল গ্রহণ করার সময় অতিবাহিত হয়ে গেছে।"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth share: ইনকামিং ফাইল"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"আপনি কি এই ফাইলটি পেতে চান?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"অন্য ডিভাইস থেকে ইনকামিং ফাইল। আপনি যে ফাইলটি পেতে চান তা নিশ্চিত করুন।"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"আগত ফাইল"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> পাঠানোর জন্য প্রস্তুত"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: <xliff:g id="FILE">%1$s</xliff:g> প্রাপ্ত করা হচ্ছে"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth share: <xliff:g id="FILE">%1$s</xliff:g> প্রাপ্ত করা হয়েছে"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: <xliff:g id="FILE">%1$s</xliff:g> ফাইল প্রাপ্ত করা হয়নি"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"সাফ করুন"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"সংরক্ষণ করুন"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"বাতিল করুন"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"আপনি Bluetooth এর মাধ্যমে যেই অ্যাকাউন্টগুলি ভাগ করতে চান সেগুলি নির্বাচন করুন। সংযোগের সময়ে আপনাকে এখনও পর্যন্ত অ্যাকাউন্টের যে কোনো অ্যাক্সেস গ্রহণ করতে হবে।"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"আপনি Bluetooth এর মাধ্যমে যে অ্যাকাউন্টগুলি শেয়ার করতে চান সেগুলি নির্বাচন করুন। সংযোগের সময়ে আপনাকে এখনো অ্যাকাউন্টের যে কোনো অ্যাক্সেস গ্রহণ করতে হবে।"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"যে স্লটগুলি বাকি আছে:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"অ্যাপ্লিকেশান আইকন"</string>
- <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth মারফত বার্তা ভাগ করার সেটিংস"</string>
+ <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth মারফত বার্তা শেয়ার করার সেটিংস"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"অ্যাকাউন্ট নির্বাচন করা যাচ্ছে না। ০টি স্লট বাকি আছে"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth অডিও সংযুক্ত হয়েছে"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth অডিওর সংযোগ বিচ্ছিন্ন হয়েছে"</string>
</resources>
diff --git a/res/values-bn-rBD/strings_pbap_client.xml b/res/values-bn-rBD/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-bn-rBD/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-bn-rBD/test_strings.xml b/res/values-bn-rBD/test_strings.xml
index 1e9145f..bc9c95a 100644
--- a/res/values-bn-rBD/test_strings.xml
+++ b/res/values-bn-rBD/test_strings.xml
@@ -2,7 +2,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="hello" msgid="1740533743008967039">"হ্যালো ওয়ার্ল্ড, TestActivity"</string>
- <string name="app_name" msgid="1203877025577761792">"Bluetooth ভাগ করা"</string>
+ <string name="app_name" msgid="1203877025577761792">"Bluetooth শেয়ার করা"</string>
<string name="insert_record" msgid="1450997173838378132">"রেকর্ড ঢোকান"</string>
<string name="update_record" msgid="2480425402384910635">"রেকর্ড নিশ্চিত করুন"</string>
<string name="ack_record" msgid="6716152390978472184">"Ack রেকর্ড"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..aca69f4
--- /dev/null
+++ b/res/values-bs-rBA/strings.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pristupite upravitelju za preuzimanja."</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Dozvoljava aplikaciji da pristupa BluetoothShare upravitelju i koristi ga za prenošenje fajlova."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Dozvoli pristup bluetooth uređaju."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na spisak dopuštenih fajlova, omogućavajući tom uređaju da šalje fajlove na ovaj uređaj bez potvrde korisnika."</string>
+ <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
+ <string name="unknown_device" msgid="9221903979877041009">"Nepoznat uređaj"</string>
+ <string name="unknownNumber" msgid="4994750948072751566">"Nepoznato"</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"Način rada u avionu"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Ne možete koristiti Bluetooth u načinu rada u avionu."</string>
+ <string name="bt_enable_title" msgid="8657832550503456572"></string>
+ <string name="bt_enable_line1" msgid="7203551583048149">"Da biste koristili Bluetooth usluge, prvo morate uključiti Bluetooth."</string>
+ <string name="bt_enable_line2" msgid="4341936569415937994">"Želite uključiti Bluetooth sada?"</string>
+ <string name="bt_enable_cancel" msgid="1988832367505151727">"Otkaži"</string>
+ <string name="bt_enable_ok" msgid="3432462749994538265">"Uključi"</string>
+ <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Prenošenje fajla"</string>
+ <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Prihvatiti dolazni fajl?"</string>
+ <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Odbij"</string>
+ <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Prihvati"</string>
+ <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Uredu"</string>
+ <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Isteklo je vrijeme prilikom prihvatanja dolaznog fajla koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazni fajl"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> sada može poslati <xliff:g id="FILE">%2$s</xliff:g>"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth dijeljenje: Prima se fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received" msgid="3324588019186687985">"Bluetooth dijeljenje: Primljen fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth dijeljenje: Fajl <xliff:g id="FILE">%1$s</xliff:g> nije primljen"</string>
+ <string name="notification_sending" msgid="3035748958534983833">"Bluetooth dijeljenje: Šalje se fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent" msgid="9218710861333027778">"Bluetooth dijeljenje: Poslan fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent_complete" msgid="302943281067557969">"Dovršeno 100%"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"Bluetooth dijeljenje: Fajl <xliff:g id="FILE">%1$s</xliff:g> nije poslan"</string>
+ <string name="download_title" msgid="3353228219772092586">"Prenošenje fajla"</string>
+ <string name="download_line1" msgid="4926604799202134144">"Šalje: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="download_line2" msgid="5876973543019417712">"Fajl: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_line3" msgid="4384821622908676061">"Veličina fajla: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="download_line4" msgid="8535996869722666525"></string>
+ <string name="download_line5" msgid="3069560415845295386">"Primanje fajla…"</string>
+ <string name="download_cancel" msgid="9177305996747500768">"Zaustavi"</string>
+ <string name="download_ok" msgid="5000360731674466039">"Sakrij"</string>
+ <string name="incoming_line1" msgid="2127419875681087545">"Šalje"</string>
+ <string name="incoming_line2" msgid="3348994249285315873">"Naziv fajla"</string>
+ <string name="incoming_line3" msgid="7954237069667474024">"Veličina"</string>
+ <string name="download_fail_line1" msgid="3846450148862894552">"Fajl nije primljen"</string>
+ <string name="download_fail_line2" msgid="8950394574689971071">"Fajl: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="download_fail_line3" msgid="3451040656154861722">"Razlog: <xliff:g id="REASON">%1$s</xliff:g>"</string>
+ <string name="download_fail_ok" msgid="1521733664438320300">"Uredu"</string>
+ <string name="download_succ_line5" msgid="4509944688281573595">"Fajl primljen"</string>
+ <string name="download_succ_ok" msgid="7053688246357050216">"Otvori"</string>
+ <string name="upload_line1" msgid="2055952074059709052">"Prima: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+ <string name="upload_line3" msgid="4920689672457037437">"Vrsta fajla: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
+ <string name="upload_line5" msgid="7759322537674229752">"Slanje fajla…"</string>
+ <string name="upload_succ_line5" msgid="5687317197463383601">"Fajl poslan"</string>
+ <string name="upload_succ_ok" msgid="7705428476405478828">"Uredu"</string>
+ <string name="upload_fail_line1" msgid="7899394672421491701">"Fajl kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" nije poslan."</string>
+ <string name="upload_fail_line1_2" msgid="2108129204050841798">"Fajl: <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="upload_fail_ok" msgid="5807702461606714296">"Pokušajte ponovo"</string>
+ <string name="upload_fail_cancel" msgid="9118496285835687125">"Zatvori"</string>
+ <string name="bt_error_btn_ok" msgid="5965151173011534240">"Uredu"</string>
+ <string name="unknown_file" msgid="6092727753965095366">"Nepoznat fajl"</string>
+ <string name="unknown_file_desc" msgid="480434281415453287">"Nema aplikacije za rukovanje ovom vrstom fajla. \n"</string>
+ <string name="not_exist_file" msgid="3489434189599716133">"Nema fajla"</string>
+ <string name="not_exist_file_desc" msgid="4059531573790529229">"Fajl ne postoji. \n"</string>
+ <string name="enabling_progress_title" msgid="436157952334723406">"Pričekajte…"</string>
+ <string name="enabling_progress_content" msgid="4601542238119927904">"Uključuje se Bluetooth…"</string>
+ <string name="bt_toast_1" msgid="972182708034353383">"Fajl će biti primljen. Pratite napredak u Ploči s obavještenjima."</string>
+ <string name="bt_toast_2" msgid="8602553334099066582">"Nije moguće primiti fajl."</string>
+ <string name="bt_toast_3" msgid="6707884165086862518">"Zaustavljeno primanje fajla kojeg šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_toast_4" msgid="4678812947604395649">"Slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+ <string name="bt_toast_5" msgid="2846870992823019494">"Slanje <xliff:g id="NUMBER">%1$s</xliff:g> fajl(ov)a, prima \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
+ <string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nema dovoljno prostora na USB pohrani da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nema dovoljno prostora na SD kartici da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_2" msgid="2965243265852680543">"Potrebni prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Obrađuje se previše zahtjeva. Pokušajte ponovo kasnije."</string>
+ <string name="status_pending" msgid="2503691772030877944">"Prenošenje fajla još nije započelo."</string>
+ <string name="status_running" msgid="6562808920311008696">"Prenošenje fajla je u toku."</string>
+ <string name="status_success" msgid="239573225847565868">"Prenošenje fajla je uspješno dovršeno."</string>
+ <string name="status_not_accept" msgid="1695082417193780738">"Sadržaj nije podržan."</string>
+ <string name="status_forbidden" msgid="613956401054050725">"Ciljni uređaj je zabranio prenošenje."</string>
+ <string name="status_canceled" msgid="6664490318773098285">"Korisnik je otkazao prenošenje."</string>
+ <string name="status_file_error" msgid="3671917770630165299">"Problem s pohranom."</string>
+ <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nema USB pohrane."</string>
+ <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nema SD kartice. Umetnite SD karticu kako biste sačuvali prenesene fajlove."</string>
+ <string name="status_connection_error" msgid="947681831523219891">"Povezivanje nije uspjelo."</string>
+ <string name="status_protocol_error" msgid="3245444473429269539">"Nije moguće pravilno obraditi zahtjev."</string>
+ <string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata greška."</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno preko Bluetootha"</string>
+ <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Primanje završeno."</string>
+ <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Slanje dovršeno."</string>
+ <string name="inbound_history_title" msgid="6940914942271327563">"Dolazna prenošenja"</string>
+ <string name="outbound_history_title" msgid="4279418703178140526">"Odlazna prenošenja"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"Historija prenošenja je prazna."</string>
+ <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Sve stavke će biti izbrisane sa spiska."</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth dijeljenje: Poslani fajlovi"</string>
+ <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth dijeljenje: Primljeni fajlovi"</string>
+ <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
+ <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspješan.</item>
+ <item quantity="few"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspješna.</item>
+ <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> neuspješnih.</item>
+ </plurals>
+ <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
+ <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspješan, %2$s</item>
+ <item quantity="few"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspješna, %2$s</item>
+ <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> uspješnih, %2$s</item>
+ </plurals>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"Obriši spisak"</string>
+ <string name="transfer_menu_open" msgid="3368984869083107200">"Otvori"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"Obriši sa spiska"</string>
+ <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Obriši"</string>
+ <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Sačuvaj"</string>
+ <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Otkaži"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Odaberite račune koje želite dijeliti preko Bluetootha. I dalje morate prihvatiti bilo koji pristup računima prilikom povezivanja."</string>
+ <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Preostalo utora:"</string>
+ <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikacije"</string>
+ <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Postavke za dijeljenje Bluetooth poruka"</string>
+ <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Nije moguće odabrati račun. Preostalo je 0 utora"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio je povezan"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio je isključen"</string>
+</resources>
diff --git a/res/values-bs-rBA/strings_pbap.xml b/res/values-bs-rBA/strings_pbap.xml
new file mode 100644
index 0000000..3030c23
--- /dev/null
+++ b/res/values-bs-rBA/strings_pbap.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Unesite ključ sesije za uređaj %1$s"</string>
+ <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Neophodan je ključ za Bluetooth sesiju"</string>
+ <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"Isteklo je vrijeme za prihvatanje veze sa uređajem %1$s"</string>
+ <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"Isteklo je vrijeme za unošenje ključa sesije sa uređajem %1$s"</string>
+ <string name="auth_notif_ticker" msgid="1575825798053163744">"Zahtjev za Obex provjeru vjerodostojnosti"</string>
+ <string name="auth_notif_title" msgid="7599854855681573258">"Ključ sesije"</string>
+ <string name="auth_notif_message" msgid="6667218116427605038">"Unesite ključ sesije za %1$s"</string>
+ <string name="defaultname" msgid="4821590500649090078">"Komplet za automobil"</string>
+ <string name="unknownName" msgid="2841414754740600042">"Nepoznato ime"</string>
+ <string name="localPhoneName" msgid="2349001318925409159">"Moje ime"</string>
+ <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
+</resources>
diff --git a/res/values-bs-rBA/strings_pbap_client.xml b/res/values-bs-rBA/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-bs-rBA/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-bs-rBA/strings_sap.xml b/res/values-bs-rBA/strings_sap.xml
new file mode 100644
index 0000000..ab906d4
--- /dev/null
+++ b/res/values-bs-rBA/strings_sap.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"Bluetooth pristup SIM-u"</string>
+ <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"Bluetooth pristup SIM-u"</string>
+ <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"Tražiti od klijenta da prekine vezu?"</string>
+ <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"Čeka se da klijent prekine vezu"</string>
+ <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"Prekini vezu"</string>
+ <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"Prisilno prekini vezu"</string>
+</resources>
diff --git a/res/values-bs-rBA/test_strings.xml b/res/values-bs-rBA/test_strings.xml
new file mode 100644
index 0000000..0d40a91
--- /dev/null
+++ b/res/values-bs-rBA/test_strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="hello" msgid="1740533743008967039">"Hello World, TestActivity"</string>
+ <string name="app_name" msgid="1203877025577761792">"Bluetooth dijeljenje"</string>
+ <string name="insert_record" msgid="1450997173838378132">"Umetni zapis"</string>
+ <string name="update_record" msgid="2480425402384910635">"Potvrdi zapis"</string>
+ <string name="ack_record" msgid="6716152390978472184">"Evidencija Ack"</string>
+ <string name="deleteAll_record" msgid="4383349788485210582">"Izbriši svu evidenciju"</string>
+ <string name="ok_button" msgid="6519033415223065454">"Uredu"</string>
+ <string name="delete_record" msgid="4645040331967533724">"Izbriši evidenciju"</string>
+ <string name="start_server" msgid="9034821924409165795">"Pokreni TCP server"</string>
+ <string name="notify_server" msgid="4369106744022969655">"Obavijesti TCP server"</string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 9bff9fb..53ce817 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepta"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"D\'acord"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"S\'ha esgotat el temps d\'espera mentre s\'acceptava un fitxer entrant de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: fitxer entrant"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Vols rebre aquest fitxer?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Està entrant un fitxer d\'un altre dispositiu. Confirma que vols rebre el fitxer."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fitxer entrant"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ja pot enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: s\'està rebent <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> rebut"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> no rebut"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Esborra"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Desa"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel·la"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecciona els comptes que vulguis compartir mitjançant el Bluetooth. Cal que acceptis l\'accés als comptes en connectar-t\'hi."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona els comptes que vulguis compartir mitjançant el Bluetooth. Cal que acceptis l\'accés als comptes en connectar-t\'hi."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espais que queden:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icona d\'aplicació"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Configuració per compartir missatges mitjançant el Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"No es pot seleccionar el compte. No queda cap espai."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Àudio per Bluetooth connectat"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Àudio per Bluetooth desconnectat"</string>
</resources>
diff --git a/res/values-ca/strings_pbap_client.xml b/res/values-ca/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ca/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 07b92ce..42363ca 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Přijmout"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Při příjmu příchozího souboru od uživatele <xliff:g id="SENDER">%1$s</xliff:g> vypršel časový limit."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Sdílení Bluetooth: Příchozí soubor"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Chcete tento soubor přijmout?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Potvrďte prosím příjem příchozího souboru z jiného zařízení."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Příchozí soubor"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> – odeslání souboru <xliff:g id="FILE">%2$s</xliff:g> je připraveno"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Sdílení Bluetooth: Příjem souboru <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Sdílení Bluetooth: Soubor <xliff:g id="FILE">%1$s</xliff:g> byl přijat"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Sdílení Bluetooth: Soubor <xliff:g id="FILE">%1$s</xliff:g> nebyl přijat"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vymazat"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Uložit"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Zrušit"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Vyberte účty, které chcete sdílet prostřednictvím rozhraní Bluetooth. Při připojování bude třeba přístup k účtům i nadále schvalovat."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vyberte účty, které chcete sdílet prostřednictvím rozhraní Bluetooth. Při připojování budete přístup k účtům muset i nadále schválit."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Zbývající sloty:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikace"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Nastavení sdílení zpráv přes Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Účet nelze vybrat. Nezbývají žádné sloty."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth Audio – připojeno"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth Audio – odpojeno"</string>
</resources>
diff --git a/res/values-cs/strings_pbap_client.xml b/res/values-cs/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-cs/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index ea8c200..2050d7b 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepter"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Der opstod timeout ved modtagelse af indgående fil fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-deling: indgående fil"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"VIl du modtage denne fil?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Indgående fil fra en anden enhed. Bekræft, at du vil modtage denne fil."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Indgående fil"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til at sende <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Modtager <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: Modtog <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Filen <xliff:g id="FILE">%1$s</xliff:g> blev ikke modtaget"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ryd"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gem"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuller"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Vælg de konti, du vil dele via Bluetooth. Du skal stadig acceptere adgang til kontiene, når du opretter forbindelse."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vælg de konti, du vil dele via Bluetooth. Du skal stadig acceptere adgang til kontiene, når du opretter forbindelse."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Pladser tilbage:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Appens ikon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Indstillinger for beskeddeling via Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Kontoen kan ikke vælges. Der er ikke flere pladser tilbage"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-lyden blev tilsluttet"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyden blev afbrudt"</string>
</resources>
diff --git a/res/values-da/strings_pbap_client.xml b/res/values-da/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-da/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 288a776..5e6e6d1 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Akzeptieren"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Die Zeit zum Empfang der eingehenden Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" ist abgelaufen."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-Freigabe: Eingehende Datei"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Möchtest du diese Datei empfangen?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Ein anderes Gerät versucht, eine Datei zu übertragen. Bestätige, dass du diese Datei empfangen möchtest."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Eingehende Datei"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> kann jetzt <xliff:g id="FILE">%2$s</xliff:g> senden."</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wird empfangen"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde empfangen"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde nicht empfangen"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Löschen"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Speichern"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Abbrechen"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Wähle die Konten aus, die du über Bluetooth freigeben möchtest. Du musst jedoch weiterhin jedem Zugriff auf die Konten zustimmen, wenn eine Verbindung hergestellt wird."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Wähle die Konten aus, die du über Bluetooth freigeben möchtest. Du musst jedoch weiterhin jedem Zugriff auf die Konten zustimmen, wenn eine Verbindung hergestellt wird."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Plätze frei:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"App-Symbol"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Einstellungen zur Bluetooth-Nachrichtenfreigabe"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Konto kann nicht ausgewählt werden. 0 Plätze frei."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-Audio verbunden"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-Audio-Verbindung aufgehoben"</string>
</resources>
diff --git a/res/values-de/strings_pbap_client.xml b/res/values-de/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-de/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 170e381..7c90422 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Αποδοχή"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Σημειώθηκε διακοπή κατά την αποδοχή ενός εισερχόμενου αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Κοινή χρήση μέσω Bluetooth: Εισερχόμενο αρχείο"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Θέλετε να λάβετε αυτό το αρχείο;"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Εισερχόμενο αρχείο από άλλη συσκευή. Επιβεβαιώστε ότι θέλετε να λάβετε αυτό το αρχείο."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Εισερχόμενο αρχείο"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Ο χρήστης <xliff:g id="SENDER">%1$s</xliff:g> πρόκειται να στείλει το αρχείο <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Κοινή χρήση μέσω Bluetooth: Λήψη του <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Κοινή χρήση μέσω Bluetooth: Ελήφθη το <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Κοινή χρήση μέσω Bluetooth: Το αρχείο <xliff:g id="FILE">%1$s</xliff:g> δεν ελήφθη"</string>
@@ -95,8 +94,8 @@
<string name="status_not_accept" msgid="1695082417193780738">"Το περιεχόμενο δεν υποστηρίζεται."</string>
<string name="status_forbidden" msgid="613956401054050725">"Δεν επιτρέπεται αυτή η μεταφορά από τη συσκευή προορισμού."</string>
<string name="status_canceled" msgid="6664490318773098285">"Η μεταφορά ακυρώθηκε από τον χρήστη."</string>
- <string name="status_file_error" msgid="3671917770630165299">"Πρόβλημα χώρου αποθήκευσης."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Δεν υπάρχει χώρος αποθήκευσης USB."</string>
+ <string name="status_file_error" msgid="3671917770630165299">"Πρόβλημα αποθηκευτικού χώρου."</string>
+ <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Δεν υπάρχει αποθηκευτικός χώρος USB."</string>
<string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Δεν υπάρχει κάρτα SD card. Εισαγάγετε μια κάρτα SD για να αποθηκεύετε τα μεταφερόμενα αρχεία."</string>
<string name="status_connection_error" msgid="947681831523219891">"Η σύνδεση δεν ήταν επιτυχής."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Δεν μπορεί να γίνει σωστός χειρισμός του αιτήματος."</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Διαγραφή"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Αποθήκευση"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ακύρωση"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Επιλέξτε τους λογαριασμούς που θέλετε να μοιραστείτε μέσω Bluetooth. Θα πρέπει ακόμη να αποδεχτείτε τυχόν αιτήματα πρόσβασης στους λογαριασμούς κατά τη σύνδεση."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Επιλέξτε τους λογαριασμούς που θέλετε να μοιραστείτε μέσω Bluetooth. Θα πρέπει ακόμη να αποδεχτείτε τυχόν αιτήματα πρόσβασης στους λογαριασμούς κατά τη σύνδεση."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Υποδοχές που απομένουν:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Εικονίδιο εφαρμογής"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Ρυθμίσεις κοινής χρήσης μηνυμάτων μέσω Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Δεν είναι δυνατή η επιλογή λογαριασμού. Απομένουν 0 υποδοχές"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Ο ήχος Bluetooth συνδέθηκε"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Ο ήχος Bluetooth αποσυνδέθηκε"</string>
</resources>
diff --git a/res/values-el/strings_pbap_client.xml b/res/values-el/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-el/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index e200e4a..735b078 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accept"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth share: Incoming file"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Do you want to receive this file?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Incoming file from another device. Confirm that you want to receive this file."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Slots left:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Application Icon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth Message Sharing Settings"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Cannot select account. 0 slots left"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
</resources>
diff --git a/res/values-en-rAU/strings_pbap_client.xml b/res/values-en-rAU/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-en-rAU/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index e200e4a..735b078 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accept"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth share: Incoming file"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Do you want to receive this file?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Incoming file from another device. Confirm that you want to receive this file."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Slots left:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Application Icon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth Message Sharing Settings"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Cannot select account. 0 slots left"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
</resources>
diff --git a/res/values-en-rGB/strings_pbap_client.xml b/res/values-en-rGB/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-en-rGB/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index e200e4a..735b078 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accept"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth share: Incoming file"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Do you want to receive this file?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Incoming file from another device. Confirm that you want to receive this file."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Slots left:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Application Icon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth Message Sharing Settings"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Cannot select account. 0 slots left"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
</resources>
diff --git a/res/values-en-rIN/strings_pbap_client.xml b/res/values-en-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-en-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 7bd205a..1d544f2 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tiempo de espera agotado al aceptar un archivo entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Compartir por Bluetooth: Archivo entrante"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"¿Quieres recibir este archivo?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Hay un archivo entrante de otro dispositivo. Confirma que quieres recibirlo."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Archivo entrante"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está listo para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: recibiendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: no se recibió el archivo <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Eliminar"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecciona las cuentas que deseas compartir a través de Bluetooth. Al conectarte, tendrás que aceptar cualquier acceso a las cuentas."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona las cuentas que deseas compartir mediante Bluetooth. Al conectarte, tendrás que aceptar cualquier acceso a las cuentas."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espacios restantes:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ícono de la aplicación"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Configuración de mensajes compartidos por Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"No puedes seleccionar la cuenta. No quedan espacios."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth conectado"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth desconectado"</string>
</resources>
diff --git a/res/values-es-rUS/strings_pbap_client.xml b/res/values-es-rUS/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-es-rUS/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b9d08b8..34a496a 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Se ha agotado el tiempo para aceptar el archivo entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: archivo entrante"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"¿Quieres recibir este archivo?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Otro dispositivo quiere enviarte un archivo. Confirma que quieres recibirlo."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Archivo entrante"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ya puede enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: recibiendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Compartir con Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> no recibido"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Ranuras libres:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icono de aplicación"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Ajustes de mensajes compartidos por Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"No se puede seleccionar la cuenta. No quedan ranuras"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio por Bluetooth conectado"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio por Bluetooth desconectado"</string>
</resources>
diff --git a/res/values-es/strings_pbap_client.xml b/res/values-es/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-es/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index a60d4b8..539113a 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Nõustun"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Esines ajalõpp sissetuleva faili aktsepteerimisel saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetoothi jagamine: sissetulev fail"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Kas soovite selle faili vastu võtta?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Sissetulev fail teisest seadmest. Kinnitage, et soovite selle faili vastu võtta."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Sissetulev fail"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Saatja <xliff:g id="SENDER">%1$s</xliff:g> on faili <xliff:g id="FILE">%2$s</xliff:g> saatmiseks valmis"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetoothi jagamine: faili <xliff:g id="FILE">%1$s</xliff:g> vastuvõtmine"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetoothi jagamine: <xliff:g id="FILE">%1$s</xliff:g> vastu võetud"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetoothi jagamine: faili <xliff:g id="FILE">%1$s</xliff:g> pole saadud"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Kustuta"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvesta"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Tühista"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Valige kontod, mida soovite Bluetoothi kaudu jagada. Ühendamisel peate ikka lubama mis tahes juurdepääsu kontodele."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Valige kontod, mida soovite Bluetoothi kaudu jagada. Ühendamisel peate ikka lubama mis tahes juurdepääsu kontodele."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Järelejäänud vabu kohti:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Rakenduse ikoon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetoothi sõnumi jagamise seaded"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Kontot ei saa valida. Pole ühtegi vaba kohta"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetoothi heli on ühendatud"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetoothi heli ühendus on katkestatud"</string>
</resources>
diff --git a/res/values-et-rEE/strings_pbap_client.xml b/res/values-et-rEE/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-et-rEE/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 95b7b4f..89718ab 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Onartu"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Ados"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen sarrerako fitxategia onartzeko denbora-muga gainditu da"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth bidezko partekatzea: sarrerako fitxategia"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Fitxategi hau jaso nahi duzu?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Sarrerako fitxategi bat bidali dizute beste gailu batetik. Berretsi fitxategia jaso nahi duzun."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Sarrerako fitxategia"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> prest dago <xliff:g id="FILE">%2$s</xliff:g> bidaltzeko"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth bidezko partekatzea: <xliff:g id="FILE">%1$s</xliff:g> fitxategia jasotzen"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth bidezko partekatzea: <xliff:g id="FILE">%1$s</xliff:g> fitxategia jaso da"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth bidezko partekatzea: ez da <xliff:g id="FILE">%1$s</xliff:g> fitxategia jaso"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Garbitu"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gorde"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Utzi"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Hautatu Bluetooth bidez partekatu nahi dituzun kontuak. Konektatzean, kontuetarako sarbidea eman behar duzu."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Hautatu Bluetooth bidez partekatu nahi dituzun kontuak. Konektatzean, berariaz eman beharko duzu kontuetarako sarbidea."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Geratzen diren erretenak:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Aplikazioaren ikonoa"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth bidez mezuak partekatzeko ezarpenak"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Ezin da hautatu kontua. Ez da erretenik geratzen."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Konektatu da Bluetooth bidezko audioa"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Deskonektatu da Bluetooth bidezko audioa"</string>
</resources>
diff --git a/res/values-eu-rES/strings_pbap_client.xml b/res/values-eu-rES/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-eu-rES/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index e462696..9e390dd 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -18,8 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"دسترسی به Download Manager."</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"به برنامه برای دسترسی به مدیر BluetoothShare و استفاده از آن برای انتقال فایلها اجازه میدهد."</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"دسترسی دستگاه موجود در لیست سفید بلوتوث."</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"به برنامه اجازه میدهد تا موقتاً یک دستگاه بلوتوث را در لیست سفید وارد کند، و به آن دستگاه اجازه میدهد بدون تأیید کاربر به این دستگاه فایل ارسال کند."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"دسترسی دستگاه موجود در فهرست سفید بلوتوث."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"به برنامه اجازه میدهد تا موقتاً یک دستگاه بلوتوث را در فهرست سفید وارد کند، و به آن دستگاه اجازه میدهد بدون تأیید کاربر به این دستگاه فایل ارسال کند."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"بلوتوث"</string>
<string name="unknown_device" msgid="9221903979877041009">"دستگاه ناشناس"</string>
<string name="unknownNumber" msgid="4994750948072751566">"ناشناس"</string>
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"پذیرفتن"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"تأیید"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"هنگام پذیرش یک فایل ورودی از \"<xliff:g id="SENDER">%1$s</xliff:g>\" وقفه زمانی پیش آمد"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"اشتراک بلوتوث: فایل ورودی"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"آیا میخواهید این فایل را دریافت کنید؟"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"فایل ورودی از یک دستگاه دیگر. لطفاً تأیید کنید که میخواهید این فایل را دریافت کنید."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"فایل ورودی"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> آماده ارسال <xliff:g id="FILE">%2$s</xliff:g> است"</string>
<string name="notification_receiving" msgid="4674648179652543984">"اشتراک بلوتوث: در حال دریافت <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"اشتراک بلوتوث: <xliff:g id="FILE">%1$s</xliff:g> دریافت شد"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"اشتراک بلوتوث: فایل <xliff:g id="FILE">%1$s</xliff:g>دریافت نشد"</string>
@@ -107,7 +106,7 @@
<string name="inbound_history_title" msgid="6940914942271327563">"انتقال های ورودی"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"انتقال های خروجی"</string>
<string name="no_transfers" msgid="3482965619151865672">"سابقه انتقال خالی است."</string>
- <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"همهٔ موارد از لیست پاک میشوند."</string>
+ <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"همهٔ موارد از فهرست پاک میشوند."</string>
<string name="outbound_noti_title" msgid="8051906709452260849">"اشتراک بلوتوث: فایلهای ارسال شده"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"اشتراک بلوتوث: فایلهای دریافت شده"</string>
<plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
@@ -118,15 +117,17 @@
<item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> مورد موفق، %2$s</item>
<item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> مورد موفق، %2$s</item>
</plurals>
- <string name="transfer_menu_clear_all" msgid="790017462957873132">"پاک کردن لیست"</string>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"پاک کردن فهرست"</string>
<string name="transfer_menu_open" msgid="3368984869083107200">"باز کردن"</string>
- <string name="transfer_menu_clear" msgid="5854038118831427492">"پاک کردن از لیست"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"پاک کردن از فهرست"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"پاک کردن"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ذخیره"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"لغو"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"حسابهایی را انتخاب کنید که میخواهید از طریق بلوتوث به اشتراک بگذارید. هنگام اتصال، همچنان باید با هر گونه دسترسی به حسابها موافقت کنید."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"حسابهایی را انتخاب کنید که میخواهید از طریق بلوتوث به اشتراک بگذارید. هنگام اتصال، همچنان باید با هر گونه دسترسی به حسابها موافقت کنید."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"شیارهای باقیمانده:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"نماد برنامه"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"تنظیمات اشتراکگذاری پیام بلوتوث"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"انتخاب حساب امکانپذیر نیست. ۰ شیار باقی مانده است"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"بلوتوث صوتی متصل شد"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ارتباط بلوتوث صوتی قطع شد"</string>
</resources>
diff --git a/res/values-fa/strings_pbap_client.xml b/res/values-fa/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-fa/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3aa4527..5f01f6f 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Hyväksy"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Yhteys aikakatkaistiin vastaanotettaessa tiedostoa lähettäjältä <xliff:g id="SENDER">%1$s</xliff:g>"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-jako: Saapuva tiedosto"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Haluatko vastaanottaa tämän tiedoston?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Toinen laite lähettää tiedostoa. Vahvista, että haluat vastaanottaa tämän tiedoston."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Saapuva tiedosto"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> on valmis lähettämään kohteen <xliff:g id="FILE">%2$s</xliff:g>."</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-jako: vastaanotetaan tiedostoa <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-jako: <xliff:g id="FILE">%1$s</xliff:g> vastaanotettu"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-jako: tiedostoa <xliff:g id="FILE">%1$s</xliff:g> ei vastaanotettu"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Poista"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Tallenna"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Peruuta"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Valitse, mitkä tilit haluat jakaa Bluetoothin kautta. Tilien käyttöoikeus pitää silti hyväksyä yhdistettäessä."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Valitse tilit, jotka haluat jakaa Bluetoothin kautta. Tilien käyttöoikeus pitää silti hyväksyä yhdistettäessä."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Paikkoja jäljellä:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Sovelluskuvake"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetoothin viestinjakoasetukset"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Tilin valinta epäonnistui. 0 paikkaa jäljellä"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-ääni liitetty"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ääni katkaistu"</string>
</resources>
diff --git a/res/values-fi/strings_pbap_client.xml b/res/values-fi/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-fi/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 6295749..fa0b330 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepter"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Expiration du délai de réception du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Partage Bluetooth : réception de fichier"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Voulez-vous recevoir ce fichier?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Fichier entrant provenant d\'un autre appareil. Confirmez que vous souhaitez recevoir ce fichier."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fichier entrant"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à envoyer le fichier « <xliff:g id="FILE">%2$s</xliff:g> »"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Partage Bluetooth : réception de <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Partage Bluetooth : <xliff:g id="FILE">%1$s</xliff:g> reçu(s)"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Partage Bluetooth : fichier <xliff:g id="FILE">%1$s</xliff:g> non reçu"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Effacer"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Enregistrer"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuler"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Sélectionnez les comptes que vous souhaitez partager par Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Sélectionnez les comptes que vous souhaitez partager par Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espaces libres :"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icône de l\'application"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Paramètres de partage des messages par Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Impossible de sélectionner le compte : aucun espace libre."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connecté"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
</resources>
diff --git a/res/values-fr-rCA/strings_pbap_client.xml b/res/values-fr-rCA/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-fr-rCA/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 53c87fb..3bfbd76 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepter"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Expiration du délai de réception du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Partage Bluetooth : réception de fichier"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Voulez-vous recevoir ce fichier ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Fichier entrant provenant d\'un autre appareil. Confirmez que vous souhaitez recevoir ce fichier."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fichier entrant"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à envoyer le fichier \"<xliff:g id="FILE">%2$s</xliff:g>\"."</string>
<string name="notification_receiving" msgid="4674648179652543984">"Partage Bluetooth : réception de <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Partage Bluetooth : <xliff:g id="FILE">%1$s</xliff:g> reçu"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Partage Bluetooth : fichier <xliff:g id="FILE">%1$s</xliff:g> non reçu"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Effacer"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Enregistrer"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuler"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Sélectionnez les comptes que vous voulez partager via le Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Sélectionnez les comptes que vous voulez partager via le Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espaces libres :"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icône de l\'application"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Paramètres de partage de la messagerie via le Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Impossible de sélectionner le compte : aucun espace libre."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connecté"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
</resources>
diff --git a/res/values-fr/strings_pbap_client.xml b/res/values-fr/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-fr/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index bb57651..530ab22 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Superouse o tempo de espera mentres se aceptaba un ficheiro entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Uso compartido por Bluetooth: ficheiro entrante"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Queres recibir este ficheiro?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Ficheiro entrante doutro dispositivo. Confirma que queres recibir este ficheiro."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ficheiro entrante"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está listo para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Uso compartido por Bluetooth: recibindo <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Uso compartido por Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Uso compartido por Bluetooth: ficheiro <xliff:g id="FILE">%1$s</xliff:g> non recibido"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecciona as contas que queres compartir a través de Bluetooth. Tes que aceptar o acceso ás contas cando te conectes."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona as contas que queres compartir a través de Bluetooth. Aínda así, tes que aceptar o acceso ás contas cando te conectes."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Rañuras restantes:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icona da aplicación"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Configuración de mensaxes compartidas por Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Non se pode seleccionar a conta. Quedan 0 rañuras"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Conectouse o audio por Bluetooth"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Desconectouse o audio por Bluetooth"</string>
</resources>
diff --git a/res/values-gl-rES/strings_pbap_client.xml b/res/values-gl-rES/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-gl-rES/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index f9ef2cb..ca39a38 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"સ્વીકારો"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ઑકે"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" તરફની એક આવનારી ફાઇલ સ્વીકારતી વખતે સમયસમાપ્તિ થઈ હતી"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth શેર: આવનરી ફાઇલો"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"શું તમે આ ફાઇલ પ્રાપ્ત કરવા માંગો છો?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"બીજા ઉપકરણથી આવનારી ફાઇલ. પુષ્ટિ કરો કે તમે આ ફાઇલ પ્રાપ્ત કરવા માંગો છો."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"આવનારી ફાઇલ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> મોકલવા માટે તૈયાર છે"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth શેર: <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત કરી રહ્યું છે"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth શેર: <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth શેર: ફાઇલ <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત થઈ નથી"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"સાફ કરો"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"સાચવો"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"રદ કરો"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"તમે Bluetooth મારફતે શેર કરવા માગતા હો તે એકાઉન્ટ્સ પસંદ કરો. તમારે હજી પણ કનેક્ટ કરતી વખતે એકાઉન્ટ્સ પરની કોઈપણ અૅક્સેસ સ્વીકારવી પડશે."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"તમે Bluetooth મારફતે શેર કરવા માગતા હો તે એકાઉન્ટ્સ પસંદ કરો. તમારે હજી પણ કનેક્ટ કરતી વખતે એકાઉન્ટ્સ પરની કોઈપણ અૅક્સેસ સ્વીકારવી પડશે."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"સ્લોટ્સ બાકી:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"એપ્લિકેશન આયકન"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth સંદેશ શેરિંગ સેટિંગ્સ"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"એકાઉન્ટ પસંદ કરી શકાતું નથી. 0 સ્લોટ્સ બાકી"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth ઑડિઓ કનેક્ટ થયું"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ઑડિઓ ડિસ્કનેક્ટ થયું"</string>
</resources>
diff --git a/res/values-gu-rIN/strings_pbap_client.xml b/res/values-gu-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-gu-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 585fc5a..ac0397a 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"स्वीकारें"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक है"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" से आने वाली फ़ाइल स्वीकार करते समय समयबाह्य हुआ."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ब्लूटूथ शेयर: आने वाली फ़ाइल"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"क्या आप यह फ़ाइल प्राप्त करना चाहते हैं?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"दूसरे डिवाइस से कोई फ़ाइल आ रही है. सुनिश्चित करें कि आप यह फ़ाइल प्राप्त करना चाहते हैं."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आवक फ़ाइल"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> भेजने के लिए तैयार है"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त कर रहा है"</string>
<string name="notification_received" msgid="3324588019186687985">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> प्राप्त नहीं हुई"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"साफ़ करें"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"सहेजें"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"अभी नहीं"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"वे खाते चुनें जिन्हें आप ब्लूटूथ के द्वारा साझा करना चाहते हैं. आपको अभी भी कनेक्ट करते समय खातों की सभी ऐक्सेस को स्वीकार करना होगा."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"वे खाते चुनें जिन्हें आप ब्लूटूथ के माध्यम से साझा करना चाहते हैं. आपको अभी भी कनेक्ट करते समय खातों के किसी भी एक्सेस को स्वीकार करना होगा."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"शेष स्लॉट:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ऐप्लिकेशन आइकन"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटूथ संदेश साझाकरण सेटिंग"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाता नहीं चुना जा सकता. 0 स्लॉट शेष"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ब्लूटूथ ऑडियो कनेक्ट किया गया"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडियो डिस्कनेक्ट किया गया"</string>
</resources>
diff --git a/res/values-hi/strings_pbap_client.xml b/res/values-hi/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-hi/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 2bff6d3..7d2d4eb 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Prihvaćam"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"U redu"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Za vrijeme primanja datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\" došlo je do privremenog prekida"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth dijeljenje: dolazna datoteka"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Želite li primiti ovu datoteku?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Dolazna datoteka s drugog uređaja. Potvrdite da želite primiti tu datoteku."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazna datoteka"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> može poslati datoteku <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Dijeljenje Bluetoothom: Primanje datoteke <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Dijeljenje Bluetoothom: Primljena datoteka <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Dijeljenje Bluetoothom: Datoteka <xliff:g id="FILE">%1$s</xliff:g> nije primljena"</string>
@@ -126,9 +125,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Brisanje"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Spremi"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Odustani"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Odaberite račune koje želite dijeliti putem Bluetootha. I dalje morate prihvatiti svako pristupanje računima prilikom povezivanja."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Odaberite račune koje želite dijeliti putem Bluetootha. I dalje morate prihvatiti svako pristupanje računima prilikom povezivanja."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Preostala mjesta:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikacije"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Postavke dijeljenja poruka putem Bluetootha"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Račun nije moguće odabrati. Nema više nijednog mjesta"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth Audio povezan"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza Bluetooth Audija prekinuta"</string>
</resources>
diff --git a/res/values-hr/strings_pbap_client.xml b/res/values-hr/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-hr/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 7b75859..63df146 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Fogadás"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Időtúllépés történt \"<xliff:g id="SENDER">%1$s</xliff:g>\" feladótól érkező fájl fogadása során"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-megosztás: beérkező fájl"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Szeretné fogadni ezt a fájlt?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Fájl érkezik egy másik készülékről. Erősítse meg, hogy szeretné megkapni ezt a fájlt."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Beérkező fájl"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> készen áll a következő küldésére: <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fogadása"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fogadva"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fájl fogadása nem sikerült"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Törlés"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Mentés"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Mégse"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Válassza ki a Bluetooth használatával megosztani kívánt fiókokat. Kapcsolódásnál még mindig el kell fogadnia a fiókokhoz való hozzáférést."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Válassza ki a Bluetooth használatával megosztani kívánt fiókokat. Kapcsolódásnál el kell fogadnia a fiókokhoz való hozzáférést is."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Szabadon maradt helyek száma:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Alkalmazás ikonja"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth-kapcsolaton keresztüli üzenetmegosztás beállításai"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"A fiók kiválasztása sikertelen. 0 hely maradt"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audió csatlakoztatva"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audió leválasztva"</string>
</resources>
diff --git a/res/values-hu/strings_pbap_client.xml b/res/values-hu/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-hu/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 412f909..ed242a9 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Ընդունել"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Լավ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"«<xliff:g id="SENDER">%1$s</xliff:g>»-ից մուտքային ֆայլի ընդունման ժամանակը սպառվեց"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth համօգտագործում՝ մուտքային ֆայլ"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Ցանկանո՞ւմ եք ստանալ այս ֆայլը:"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Առկա է մուտքային ֆայլ այլ սարքից: Հաստատեք, որ ցանկանում եք ստանալ այս ֆայլը:"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Մուտքային ֆայլ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>-ը պատրաստ է ուղարկելու <xliff:g id="FILE">%2$s</xliff:g> ֆայլը"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth համօգտագործում՝ <xliff:g id="FILE">%1$s</xliff:g>-ը ստացվում է"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth համօգտագործում՝ ստացվեց <xliff:g id="FILE">%1$s</xliff:g>-ը"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth համօգտագործում՝ <xliff:g id="FILE">%1$s</xliff:g> ֆայլը չի ստացվել"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ջնջել"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Պահել"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Չեղարկել"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Ընտրեք հաշիվները, որոնք ցանկանում եք համօգտագործել Bluetooth-ի միջոցով: Ամեն դեպքում, կապակցվելիս պետք է ընդունեք հաշիվներն օգտագործելու թույլտվությունը:"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Ընտրեք հաշիվները, որոնք ցանկանում եք հասանելի դարձնել Bluetooth-ի միջոցով: Ամեն դեպքում, կապակցվելիս պետք է ընդունեք հաշիվներն օգտագործելու թույլտվությունը:"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Մնացած տողերի քանակը՝"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Հավելվածի պատկերակը"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Հաղորդագրությունների համօգտագործման Bluetooth-ի կարգավորումներ"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Հնարավոր չէ ընտրել հաշիվը: Տող չի մնացել"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth աուդիոն կապակցված է"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth աուդիոն անջատված է"</string>
</resources>
diff --git a/res/values-hy-rAM/strings_pbap_client.xml b/res/values-hy-rAM/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-hy-rAM/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index a44f100..fd81046 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Terima"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Oke"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Terjadi waktu tunggu saat menerima file masuk dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Berbagi bluetooth: File masuk"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Apakah Anda ingin menerima file ini?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"File masuk dari perangkat lain. Konfirmasikan bahwa Anda ingin menerima file ini."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"File masuk"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> siap mengirim <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Berbagi Bluetooth: Menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Berbagi Bluetooth: Telah menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Berbagi Bluetooth: File <xliff:g id="FILE">%1$s</xliff:g> tidak diterima"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Hapus"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Simpan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Batal"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Pilih akun yang ingin Anda bagikan melalui Bluetooth. Anda harus menerima akses apa pun ke akun saat menyambungkan."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pilih akun yang ingin Anda bagikan melalui Bluetooth. Anda masih harus menerima akses apa pun ke akun saat menghubungkan."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Sisa Slot:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikon Aplikasi"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Setelan Berbagi Pesan Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Tidak dapat memilih akun. Sisa 0 slot"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio terhubung"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio terputus"</string>
</resources>
diff --git a/res/values-in/strings_pbap_client.xml b/res/values-in/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-in/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 86116ea..c0646b2 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Samþykkja"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Í lagi"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Rann út á tíma við að samþykkja skrá sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-deiling: Skrá berst"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Viltu taka á móti þessari skrá?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Skrá berst frá öðru tæki. Staðfestu að þú viljir taka á móti þessari skrá."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Skrá á innleið"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er tilbúin(n) að senda <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deiling: Tekur á móti <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-deiling: Skráin <xliff:g id="FILE">%1$s</xliff:g> var móttekin"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deiling: Skráin <xliff:g id="FILE">%1$s</xliff:g> var ekki móttekin"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Hreinsa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Vista"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Hætta við"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Veldu reikningana sem þú vilt deila í gegnum Bluetooth. Þú þarft samt að samþykkja allan aðgang að reikningunum við tengingu."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Veldu reikningana sem þú vilt deila í gegnum Bluetooth. Þú þarft samt að samþykkja allan aðgang að reikningunum við tengingu."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Hólf eftir:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Forritstákn"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Deilingarstillingar Bluetooth-skilaboða"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Ekki er hægt að velja reikning. Engin hólf eftir"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-hljóð tengt"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-hljóð aftengt"</string>
</resources>
diff --git a/res/values-is-rIS/strings_pbap_client.xml b/res/values-is-rIS/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-is-rIS/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index d141c51..f922d0d 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accetta"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Si è verificato un timeout durante l\'accettazione di un file in arrivo da \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: file in arrivo"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Vuoi ricevere questo file?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"File in arrivo da un altro dispositivo. Conferma che vuoi ricevere il file."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"File in arrivo"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> è pronto per inviare <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: ricezione <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ricevuto"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: file <xliff:g id="FILE">%1$s</xliff:g> non ricevuto"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Cancella"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salva"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annulla"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Seleziona gli account che desideri condividere tramite Bluetooth. Devi comunque accettare tutti gli accessi agli account durante la connessione."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Seleziona gli account che desideri condividere tramite Bluetooth. Devi comunque accettare tutti gli accessi agli account durante la connessione."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Slot rimanenti:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icona applicazione"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Impostazioni di condivisione dei messaggi Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Impossibile selezionare l\'account. Nessuno slot rimanente"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connesso"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth disconnesso"</string>
</resources>
diff --git a/res/values-it/strings_pbap_client.xml b/res/values-it/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-it/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 0da2b69..0aa6ac3 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"קבל"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"אישור"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"תם הזמן הקצוב לקבלת קובץ נכנס מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"שיתוף Bluetooth: קובץ נכנס"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"האם ברצונך לקבל קובץ זה?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"מכשיר אחר שולח אליך קובץ. אשר את קבלת הקובץ."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"קובץ שהתקבל"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> מוכן לשלוח את <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"שיתוף Bluetooth: מקבל את <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"שיתוף Bluetooth: התקבל <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"שיתוף Bluetooth: הקובץ <xliff:g id="FILE">%1$s</xliff:g> לא התקבל"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"נקה"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"שמור"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"בטל"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"בחר את חשבונות שברצונך לשתף באמצעות Bluetooth. עדיין יהיה עליך לאשר גישה אל החשבונות בעת החיבור."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"בחר בחשבונות שברצונך לשתף באמצעות Bluetooth. עדיין יהיה עליך לאשר גישה אל החשבונות בעת החיבור."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"מקומות נותרים:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"סמל אפליקציה"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"הגדרות לשיתוף הודעות ב-Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"לא ניתן לבחור חשבון. נותרו 0 מקומות"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"אודיו Bluetooth מחובר"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"אודיו Bluetooth מנותק"</string>
</resources>
diff --git a/res/values-iw/strings_pbap_client.xml b/res/values-iw/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-iw/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 4e5a5f7..9ded17b 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"承諾"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイル受信中に接続がタイムアウトしました"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth共有: ファイル着信"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"このファイルを受信しますか?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"別の端末からファイルが着信しました。このファイルを受信するかどうか確認してください。"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"着信ファイル"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>さんが<xliff:g id="FILE">%2$s</xliff:g>を送信できるようになりました"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth共有: <xliff:g id="FILE">%1$s</xliff:g>を受信中"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth共有: <xliff:g id="FILE">%1$s</xliff:g>を受信済み"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth共有: ファイル<xliff:g id="FILE">%1$s</xliff:g>の受信に失敗"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"消去"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"保存"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"キャンセル"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetoothを介して共有するアカウントを選択してください。接続中はアカウントへのアクセスをすべて承認する必要があります。"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth を介して共有するアカウントを選択してください。接続中はアカウントへのアクセスをすべて承認する必要があります。"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"残りスロット数:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"アプリアイコン"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetoothメッセージ共有の設定"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"アカウントを選択できません。残りスロット数が0です"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth オーディオは接続されています"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth オーディオは接続を解除されています"</string>
</resources>
diff --git a/res/values-ja/strings_pbap_client.xml b/res/values-ja/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ja/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 7067f38..12dffc8 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"მიღება"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"კარგი"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"„<xliff:g id="SENDER">%1$s</xliff:g>“-გან ფაილის შემომავალი ფაილის მიღების დრო ამოიწურა"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth გაზიარება: შემომავალი ფაილი"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"გსურთ ამ ფაილის მიღება?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"შემომავალი ფაილი სხვა მოწყობილობიდან. დაადასტურეთ, რომ გსურთ ფაილის მიღება."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"შემომავალი ფაილი"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> მზად არის <xliff:g id="FILE">%2$s</xliff:g>-ის გასაგზავნად"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth გაზიარება: <xliff:g id="FILE">%1$s</xliff:g> ფაილის მიღება"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth გაზიარება: მიღებულია <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth გაზიარება: ფაილი <xliff:g id="FILE">%1$s</xliff:g> არ მიღებულა"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ამოშლა"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"შენახვა"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"გაუქმება"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"აირჩიეთ ანგარიშები, რომელთა გაზიარებაც გსურთ Bluetooth-ის მეშვეობით. ანგარიშებზე ნებისმიერი წვდომის დადასტურება მაინც მოგიწევთ დაკავშირებისას."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"აირჩიეთ ანგარიშები, რომელთა გაზიარებაც Bluetooth-ის მეშვეობით გსურთ. დაკავშირებისას ანგარიშებზე წვდომის დადასტურება მაინც მოგიწევთ."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"დარჩენილი სლოტი:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"აპლიკაციის ხატულა"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth შეტყობინების გაზიარების პარამეტრები"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ანგარიშის არჩევა ვერ ხერხდება. დარჩენილია 0 სლოტი"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth აუდიო დაკავშირებულია"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth აუდიო გათიშულია"</string>
</resources>
diff --git a/res/values-ka-rGE/strings_pbap_client.xml b/res/values-ka-rGE/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ka-rGE/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 4475db1..65e8de1 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Қабылдау"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Жарайды"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды қабылдау барысында уақыт аяқталды."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth бөлісу: Келген файл"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Бұл файлды алуды қалайсыз ба?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Басқа құрылғы арқылы келген файл. Бұл файлды қабылдауды қалайтыныңызды растаңыз."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Кіріс файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> жіберуге дайын"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлын қабылдауда"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлы қабылданды"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлы қабылданбады"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Өшіру"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сақтау"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Бас тарту"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth арқылы бөлісетін есептік жазбаларды таңдаңыз. Бұл жолы да қосылу кезінде есептік жазбаларға кіру мүмкіндігі болатынын қабылдау керек."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth арқылы бөлісетін есептік жазбаларды таңдаңыз. Әлі де қосылу кезінде есептік жазбаларға кез келген қатынасуды қабылдау керек."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Қалған слоттар:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Қолданба белгішесі"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth арқылы хабар бөлісу параметрлері"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Есептік жазбаны таңдау мүмкін емес. 0 слот қалды"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth дыбысы қосылды"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth дыбысы ажыратылды"</string>
</resources>
diff --git a/res/values-kk-rKZ/strings_pbap_client.xml b/res/values-kk-rKZ/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-kk-rKZ/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 1198c12..fb9328f 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ទទួល"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"យល់ព្រម"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"អស់ពេលទទួលឯកសារចូលពី \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ការចែករំលែកប៊្លូធូស៖ ឯកសារចូល"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"តើអ្នកចង់ទទួលឯកសារនេះឬ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ឯកសារចូលពីឧបករណ៍ផ្សេងទៀត។ បញ្ជាក់ថាអ្នកចង់ទទួលបានឯកសារនេះ។"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ឯកសារចូល"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ត្រៀមរួចរាល់ក្នុងការផ្ញើ <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ការចែករំលែកប៊្លូធូស៖ ទទួល <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"ការចែករំលែកប៊្លូធូស៖ បានទទួល <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ការចែករំលែកប៊្លូធូស៖ មិនបានទទួលឯកសារ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"សម្អាត"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"រក្សាទុក"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"បោះបង់"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលការចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលយកលទ្ធភាពចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"រន្ធនៅសល់៖"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"រូបតំណាងកម្មវិធី"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"កំណត់ការចែករំលែកសារតាមប៊្លូធូស"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"មិនអាចជ្រើសគណនីទេ អស់រន្ធនៅសល់ហើយ។"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"សំឡេងប៊្លូធូសត្រូវបានភ្ជាប់"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"សំឡេងប៊្លូធូសត្រូវបានផ្តាច់"</string>
</resources>
diff --git a/res/values-km-rKH/strings_pbap_client.xml b/res/values-km-rKH/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-km-rKH/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 7ff9fb0..27baa1f 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ಸ್ವೀಕರಿಸಿ"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ಸರಿ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ಅವರಿಂದ ಫೈಲ್ ಸ್ವೀಕರಿಸುವಾಗ ಕಾಲಾವಧಿ ಮುಕ್ತಾಯಗೊಂಡಿದೆ"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ಬ್ಲೂಟೂತ್ ಹಂಚಿಕೆ: ಒಳಬರುವ ಫೈಲ್"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ಈ ಫೈಲ್ ಅನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಬಯಸುವಿರಾ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ಮತ್ತೊಂದು ಸಾಧನದಿಂದ ಒಳಬರುವ ಫೈಲ್. ಈ ಫೈಲ್ ಸ್ವೀಕರಿಸಲು ನೀವು ಬಯಸುತ್ತೀರಾ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿ."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ಒಳಬರುತ್ತಿರುವ ಫೈಲ್"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ಅವರು <xliff:g id="FILE">%2$s</xliff:g> ಫೈಲ್ ಕಳುಹಿಸಲು ಸಿದ್ಧವಾಗಿದ್ದಾರೆ"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ಬ್ಲೂಟೂತ್ ಹಂಚಿಕೆ: <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="notification_received" msgid="3324588019186687985">"ಬ್ಲೂಟೂತ್ ಹಂಚಿಕೆ: <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಲಾಗಿದೆ"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ಬ್ಲೂಟೂತ್ ಹಂಚಿಕೆ: ಫೈಲ್ <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಿಲ್ಲ"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ತೆರವುಗೊಳಿಸು"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ಉಳಿಸು"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ರದ್ದುಮಾಡು"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"ಬ್ಲೂಟೂತ್ ಮೂಲಕ ಹಂಚಿಕೊಳ್ಳಲು ಬಯಸುವ ಖಾತೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಸಂಪರ್ಕಿಸುವಾಗ ಖಾತೆಗಳಿಗೆ ಯಾವುದೇ ಪ್ರವೇಶವನ್ನು ನೀವು ಈಗಲೂ ಒಪ್ಪಿಕೊಳ್ಳಬೇಕಾಗುತ್ತದೆ."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ಬ್ಲೂಟೂತ್ ಮೂಲಕ ಹಂಚಿಕೊಳ್ಳಲು ಬಯಸುವ ಖಾತೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಸಂಪರ್ಕಪಡಿಸುವಾಗ ಖಾತೆಗಳಿಗೆ ಯಾವುದೇ ಪ್ರವೇಶವನ್ನು ನೀವು ಈಗಲೂ ಸಮ್ಮತಿಸಬೇಕಾಗುತ್ತದೆ."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ಉಳಿದಿರುವ ಸ್ಲಾಟ್ಗಳು:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ಅಪ್ಲಿಕೇಶನ್ ಐಕಾನ್"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ಬ್ಲೂಟೂತ್ ಸಂದೇಶ ಹಂಚಿಕೆ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ಖಾತೆಯನ್ನು ಆಯ್ಕೆಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. 0 ಸ್ಲಾಟ್ಗಳು ಉಳಿದಿವೆ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ"</string>
</resources>
diff --git a/res/values-kn-rIN/strings_pbap_client.xml b/res/values-kn-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-kn-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1cd8ee2..b780341 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"수락"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"확인"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보내는 파일을 수락하는 동안 제한 시간을 초과했습니다."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"블루투스 공유: 수신 파일"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"이 파일을 받으시겠습니까?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"다른 기기에서 파일을 전송 중입니다. 이 파일을 수신할 것인지 확인하세요."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"수신 파일"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>님이 <xliff:g id="FILE">%2$s</xliff:g>을(를) 보낼 준비가 되었습니다."</string>
<string name="notification_receiving" msgid="4674648179652543984">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 받는 중"</string>
<string name="notification_received" msgid="3324588019186687985">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 받음"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 파일이 수신되지 않았습니다."</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"지우기"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"저장"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"취소"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"블루투스를 통해 공유하려는 계정을 선택하세요. 연결할 때 계정에 대한 모든 액세스를 수락해야 합니다."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"블루투스를 통해 공유하려는 계정을 선택하세요. 연결할 때 계정에 대한 모든 액세스를 수락해야 합니다."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"남은 슬롯:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"애플리케이션 아이콘"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"블루투스 메시지 공유 설정"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"계정을 선택할 수 없습니다. 남은 슬롯이 없습니다."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"블루투스 오디오가 연결됨"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"블루투스 오디오가 연결 해제됨"</string>
</resources>
diff --git a/res/values-ko/strings_pbap_client.xml b/res/values-ko/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ko/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 36689c4..78f7ce2 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Кабыл алуу"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды алуу мөөнөтү өтүп кетти."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth бөлүшүү: Алынган файл"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Бул файлды алгыңыз келеби?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Башка түзмөктөн келген файл. Бул файлды алгыңыз келгенин ырастаңыз."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Кирүүчү файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> жөнөтүүгө даяр"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынууда"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынды"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынган жок"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Тазалоо"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сактоо"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Жокко чыгаруу"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташып жатканда, каттоо эсептерине кирүү мүмкүнчүлүгүн кабыл алышыңыз керек."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Калган көзөнөктөр:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Колдонмонун сөлөкөтү"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Каттоо эсебин тандоо мүмкүн эмес. 0 көзөнөк калды"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth аудио туташты"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудио ажыратылды"</string>
</resources>
diff --git a/res/values-ky-rKG/strings_pbap_client.xml b/res/values-ky-rKG/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ky-rKG/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index e91d853..6279cb0 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ຮັບເອົາ"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ຕົກລົງ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"ເກີດມີການໝົດເວລາໃນລະຫວ່າງທີ່ກຳລັງຮັບເອົາໄຟລ໌ທີ່ເຂົ້າມາຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ແບ່ງປັນໃນ Bluetooth: ມີໄຟລ໌ເຂົ້າມາ"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ທ່ານຕ້ອງການທີ່ຮັບເອົາໄຟລ໌ນີ້ບໍ່?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ມີໄຟລ໌ກຳລັງເຂົ້າມາຈາກອຸປະກອນອື່ນ. ຢືນຢັນວ່າທ່ານຕ້ອງການຮັບເອົາໄຟລ໌ນີ້."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ໄຟລ໌ເຂົ້າມາ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ພ້ອມທີ່ຈະສົ່ງ <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ແບ່ງປັນໃນ Bluetooth: ກຳລັງຮັບເອົາ <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"ແບ່ງປັນໃນ Bluetooth: ໄດ້ຮັບ <xliff:g id="FILE">%1$s</xliff:g> ແລ້ວ"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ແບ່ງປັນໃນ Bluetooth: ບໍ່ໄດ້ຮັບ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ລຶບ"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ບັນທຶກ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ຍົກເລີກ"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"ເລືອກບັນຊີທີ່ທ່ານຕ້ອງການແບ່ງປັນຜ່ານ Bluetooth. ທ່ານຍັງຄົງຕ້ອງຍອມຮັບການເຂົ້າເຖິງໃດໆຕໍ່ບັນຊີຕ່າງໆເມື່ອທຳການເຊື່ອມຕໍ່."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ເລືອກບັນຊີທີ່ທ່ານຕ້ອງການແບ່ງປັນຜ່ານ Bluetooth. ທ່ານຍັງຈຳເປັນຕ້ອງຍອມຮັບທຸກການເຂົ້າເຖິງບັນຊີຕ່າງໆໃນເວລາເຊື່ອມຕໍ່."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ຊ່ອງຊ້າຍ:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ໄອຄອນແອັບພລິເຄຊັນ"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ການຕັ້ງຄ່າແບ່ງປັນຂໍ້ຄວາມ Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ບໍ່ສາມາດເລືອກບັນຊີໄດ້. ເຫຼືອ 0 ຊ່ອງ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ເຊື່ອມຕໍ່ສຽງ Bluetooth ແລ້ວ"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ຕັດການເຊື່ອມຕໍ່ສຽງ Bluetooth ແລ້ວ"</string>
</resources>
diff --git a/res/values-lo-rLA/strings_pbap_client.xml b/res/values-lo-rLA/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-lo-rLA/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4596cbb..a7ae86f 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Priimti"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Gerai"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Baigėsi laikas priimant gaunamą failą iš <xliff:g id="SENDER">%1$s</xliff:g>"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"„Bluetooth“ bendrinimas: gaunamas failas"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Ar norite gauti šį failą?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Gaunamas failas iš kito įrenginio. Patvirtinkite, kad norite gauti šį failą."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gaunamas failas"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> pasiruošęs (-usi) siųsti <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"„Bluetooth“ bendrinimas: gaunamas <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"„Bluetooth“ bendrinimas: <xliff:g id="FILE">%1$s</xliff:g> gautas"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"„Bluetooth“ bendrinimas: <xliff:g id="FILE">%1$s</xliff:g> failas negautas"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Išvalyti"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Išsaugoti"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Atšaukti"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Pasirinkite paskyras, kurias norite bendrinti per „Bluetooth“. Jungiantis vis tiek reikės suteikti prieigą prie paskyrų."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pasirinkite o paskyras, kurias norite bendrinti per „Bluetooth“. Jungiantis vis tiek reikės suteikti prieigą prie paskyrų."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Liko sričių:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Programos piktograma"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"„Bluetooth“ pranešimų bendrinimo nustatymai"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Negalima pasirinkti paskyros. Neliko jokių sričių"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"„Bluetooth“ garsas prijungtas"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"„Bluetooth“ garsas atjungtas"</string>
</resources>
diff --git a/res/values-lt/strings_pbap_client.xml b/res/values-lt/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-lt/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index b1e99e8..7a68240 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Piekrist"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Labi"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Saņemot ienākošu failu no saņēmēja <xliff:g id="SENDER">%1$s</xliff:g>, radās noildze."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth Share: ienākošais fails"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Vai vēlaties saņemt šo failu?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"No citas ierīces tiek sūtīts fails. Apstipriniet, ka vēlaties saņemt šo failu."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ienākošais fails"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Sūtītājs <xliff:g id="SENDER">%1$s</xliff:g> ir gatavs nosūtīt failu <xliff:g id="FILE">%2$s</xliff:g>."</string>
<string name="notification_receiving" msgid="4674648179652543984">"Kopīgošana, izmantojot Bluetooth: notiek faila <xliff:g id="FILE">%1$s</xliff:g> saņemšana"</string>
<string name="notification_received" msgid="3324588019186687985">"Kopīgošana, izmantojot Bluetooth: saņemts fails <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Kopīgošana, izmantojot Bluetooth: fails <xliff:g id="FILE">%1$s</xliff:g> netika saņemts"</string>
@@ -126,9 +125,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Notīrīšana"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Saglabāt"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Atcelt"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Atlasiet kontus, kurus vēlaties koplietot, izmantojot Bluetooth. Kad tiks izveidots savienojums, jums būs arī jāapstiprina piekļuve kontiem."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Atlasiet kontus, kurus vēlaties koplietot, izmantojot Bluetooth. Kad tiks izveidots savienojums, jums būs arī jāapstiprina piekļuve kontiem."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Atlikušie sloti:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Lietojumprogrammas ikona"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth ziņojumu kopīgošanas iestatījumi"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Nevar atlasīt kontu. Atlicis 0 slotu."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Izveidots savienojums ar Bluetooth audio"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Pārtraukts savienojums ar Bluetooth audio"</string>
</resources>
diff --git a/res/values-lv/strings_pbap_client.xml b/res/values-lv/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-lv/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index ae817cc..550b62a 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Прифати"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Во ред"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Времето истече додека се прифаќаше дојдовната датотека од „<xliff:g id="SENDER">%1$s</xliff:g>“."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Сподели преку Bluetooth: Дојдовна датотека"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Сакате да ја примите оваа датотека?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Дојдовна датотека од друг уред. Потврдете дека сакате да ја примите оваа датотека."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Дојдовна датотека"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> е подготвен за испраќање на <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Сподели преку Bluetooth: Се прима <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Сподели преку Bluetooth: Примена <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Сподели преку Bluetooth: Датотеката <xliff:g id="FILE">%1$s</xliff:g> не е примена."</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Исчисти"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Зачувај"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Откажи"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Изберете ги сметките што сакате да ги споделите преку Bluetooth. Сепак, ќе мора да го прифаќате секој пристап до сметките при поврзувањето."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Изберете ги сметките што сакате да ги споделите преку Bluetooth. Сепак, ќе мора да го прифаќате секој пристап до сметките при поврзување."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Слободни отвори:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Икона на апликацијата"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Поставки за споделување пораки преку Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Не може да се избере сметка. 0 слободни отвори"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Аудиото преку Bluetooth е поврзано"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиото преку Bluetooth е исклучено"</string>
</resources>
diff --git a/res/values-mk-rMK/strings_pbap_client.xml b/res/values-mk-rMK/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-mk-rMK/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 3ca842d..7490390 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"സ്വീകരിക്കുക"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ശരി"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നും ഒരു ഇൻകമിംഗ് ഫയൽ സ്വീകരിക്കുമ്പോൾ ഒരു കാലഹരണപ്പെടൽ സംഭവിച്ചു"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ബ്ലൂടൂത്ത് പങ്കിടൽ: ഇൻകമിംഗ് ഫയൽ"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"നിങ്ങൾക്ക് ഈ ഫയൽ നേടണോ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"മറ്റൊരു ഉപകരണത്തിൽ നിന്നുള്ള ഇൻകമിംഗ് ഫയൽ. ഈ ഫയൽ നിങ്ങൾക്ക് നേടണമെങ്കിൽ സ്ഥിരീകരിക്കുക."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ഇൻകമിംഗ് ഫയൽ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> അയയ്ക്കാൻ <xliff:g id="SENDER">%1$s</xliff:g> തയ്യാറാണ്"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> എന്ന ഫയൽ ലഭിക്കുന്നു"</string>
<string name="notification_received" msgid="3324588019186687985">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> ലഭിച്ചു"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> എന്ന ഫയൽ ലഭിച്ചില്ല."</string>
@@ -122,9 +121,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"മായ്ക്കുക"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"സംരക്ഷിക്കുക"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"റദ്ദാക്കുക"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"നിങ്ങൾക്ക് Bluetooth മുഖേന പങ്കിടേണ്ട അക്കൗണ്ടുകൾ തിരഞ്ഞെടുക്കുക. കണക്റ്റുചെയ്യുമ്പോൾ അക്കൗണ്ടുകളിലേക്കുള്ള ഏത് ആക്സസ്സും നിങ്ങൾ തുടർന്നും അംഗീകരിക്കേണ്ടതാണ്."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"നിങ്ങൾക്ക് Bluetooth വഴി പങ്കിടേണ്ട അക്കൗണ്ടുകൾ തിരഞ്ഞെടുക്കുക. കണക്റ്റുചെയ്യുമ്പോൾ അക്കൗണ്ടുകളിലേക്കുള്ള ഏത് ആക്സസും നിങ്ങൾ തുടർന്നും അംഗീകരിക്കേണ്ടതാണ്."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ശേഷിക്കുന്ന സ്ലോട്ടുകൾ:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"അപ്ലിക്കേഷൻ ഐക്കൺ"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth സന്ദേശ പങ്കിടൽ ക്രമീകരണം"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"അക്കൗണ്ട് തിരഞ്ഞെടുക്കാനാകില്ല. 0 സ്ലോട്ടുകൾ ശേഷിക്കുന്നു"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth ഓഡിയോ കണക്റ്റുചെയ്തു"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ഓഡിയോ വിച്ഛേദിച്ചു"</string>
</resources>
diff --git a/res/values-ml-rIN/strings_pbap_client.xml b/res/values-ml-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ml-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index b824f42..3f0e911 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Зөвшөөрөх"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Тийм"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирж байгаа файлыг зөвшөөрөх явцад хугацаа хэтэрсэн байна"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth хуваалцах: Ирж байгаа файл"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Та энэ файлыг хүлээн авахыг хүсэж байна уу?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Өөр төхөөрөмжөөс ирж байгаа файл. Та энэ файлыг хүлээн авахыг хүсэж байгаагаа баталгаажуулна уу."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ирж буй файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> нь <xliff:g id="FILE">%2$s</xliff:g>-г илгээхэд бэлэн"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth хуваалцах: Хүлээн авч байна <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth хуваалцах: Хүлээн авсан <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth хуваалцах: Файл <xliff:g id="FILE">%1$s</xliff:g> хүлээж аваагүй"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Арилгах"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Хадгалах"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Цуцлах"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth-ээр дамжуулан хуваалцахыг хүсч буй дансаа сонго. Та холбогдож байх үедээ ч данс руу нэвтрэх ямар ч хандалтуудыг хүлээн авах боломжтой."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth-ээр хуваалцахыг хүсч буй дансаа сонго. Холболт хийх үед данс руу нэвтрэх аливаа хандалтыг хүлээн зөвшөөрөх хэрэгтэй болно."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Үлдсэн слот:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Аппликешний дүрс"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth мессеж хуваалцах тохиргоо"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Дансыг сонгох боломжгүй. 0 слот үлдсэн"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth аудиог холбосон"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудиог салгасан"</string>
</resources>
diff --git a/res/values-mn-rMN/strings_pbap_client.xml b/res/values-mn-rMN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-mn-rMN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index b8e1788..bd43ddd 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"स्वीकारा"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल स्वीकार करताना वेळ संपली."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ब्लूटुथ शेअर: येणारी फाइल"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"आपण ही फाइल प्राप्त करू इच्छिता?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"दुसर्या डिव्हाइस वरील येणारी फाइल. ही फाइल आपण प्राप्त करू इच्छिता याची पुष्टी करा."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"येणारी फाईल"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> पाठविण्यासाठी <xliff:g id="SENDER">%1$s</xliff:g> तयार आहे"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ब्लूटुथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त करीत आहे"</string>
<string name="notification_received" msgid="3324588019186687985">"ब्लूटुथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त केली"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटुथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> फाइल प्राप्त केली नाही"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"साफ करा"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"जतन करा"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द करा"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"आपण ब्लूटुथद्वारे सामायिक करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप आपल्याला खात्यांमधील कोणताही प्रवेश स्वीकारण्याची आवश्यकता आहे."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"आपण ब्लूटुथद्वारे सामायिक करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप आपल्याला खात्यांमधील कोणताही प्रवेश स्वीकारण्याची आवश्यकता आहे."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"स्लॉट शिल्लक:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"अनुप्रयोग चिन्ह"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटुथ संदेश सामायिकरण सेटिंग्ज"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाते निवडू शकत नाही. 0 स्लॉट शिल्लक"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ब्लूटुथ ऑडिओ कनेक्ट केला"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटुथ ऑडिओ डिस्कनेक्ट केला"</string>
</resources>
diff --git a/res/values-mr-rIN/strings_pbap_client.xml b/res/values-mr-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-mr-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 4f52adb..37971e2 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Terima"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Berlaku tamat masa semasa menerima fail masuk daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Perkongsian Bluetooth: Fail masuk"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Adakah anda mahu menerima fail ini?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Fail masuk daripada peranti lain. Sila sahkan bahawa anda mahu menerima fail ini."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fail masuk"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> sudah bersedia untuk menghantar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Perkongsian Bluetooth: Menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Perkongsian Bluetooth: Diterima <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Perkongsian Bluetooth: Fail <xliff:g id="FILE">%1$s</xliff:g> tidak diterima"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Padam bersih"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Simpan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Batal"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Pilih akaun yang ingin anda kongsikan melalui Bluetooth. Anda masih perlu menerima sebarang akses kepada akaun semasa menyambung."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pilih akaun yang anda ingin kongsikan melalui Bluetooth. Anda masih perlu menerima sebarang akses kepada akaun semasa menyambung."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Slot yang tinggal:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikon Aplikasi"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Tetapan Perkongsian Mesej Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Tidak boleh memilih akaun. 0 slot yang tinggal"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth disambungkan"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth diputuskan sambungannya"</string>
</resources>
diff --git a/res/values-ms-rMY/strings_pbap_client.xml b/res/values-ms-rMY/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ms-rMY/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 9be7b60..eb17fca 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"လက်ခံရန်"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ကောင်းပြီ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"မှ အဝင်ဖိုင်ကို လက်ခံနေစဥ် အချိန်ကုန်ဆုံးသွားပါပြီ"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ဘလူးတုသ် ဝေမျှမှု - အဝင်ဖိုင်"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ဤဖိုင်ကို လက်ခံမည်လား?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"အခြားစက်မှ ဝင်လာသော ဖိုင်ရှိသည်။ ထိုဖိုင်ကို လက်ခံရန် အတည်ပြုပါ။"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"အဝင် ဖိုင်"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> က <xliff:g id="FILE">%2$s</xliff:g>ကို ပို့ရန် အသင့်ပါ"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ဘလူးတုသ် ဝေမျှမှု - <xliff:g id="FILE">%1$s</xliff:g> လက်ခံနေသည်"</string>
<string name="notification_received" msgid="3324588019186687985">"ဘလူးတုသ် ဝေမျှမှု - ရရှိပြီး<xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ဘလူးတုသ် ဝေမျှမှု - ဖိုင် <xliff:g id="FILE">%1$s</xliff:g> မရရှိပါ"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ရှင်းလင်းရန်"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"သိမ်းဆည်းရန်"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ဖျက်သိမ်းရန်"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"သင် ဘလူးတုသ်မှတဆင့် မျှဝေလိုသော အကောင့်အသစ်များအား ရွေးချယ်ပါ။ ချိတ်ဆက်နေစဉ် အကောင့်များအား ဝင်ရောက်သုံးခွင့်များကို သင်လက်ခံရန် လိုအပ်ပါဦးမည်။"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ဘလူးတုသ်မှ မျှဝေလိုသော အကောင့်များကို ရွေးပါ။ ချိတ်ဆက်သည့်အခါ အကောင့်များအား ဝင်ခွင့်ပြုဖို့ လက်ခံပေးရပါလိမ့်ဦးမည်။"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ကျန်နေသည့် အပေါက်များ:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"အပလီကေးရှင်း အိုင်ကွန်"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ဘလူးတုသ် စာ မျှဝေရေး ဆက်တင်များ"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ရွေးထားသည့် အကောင့်ကို မရွေးနိုင်ပါ။ သုည အပေါက်များ ကျန်နေ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ဘလူးတုသ်အသံ ချိတ်ဆက်ပြီးပါပြီ"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ဘလူးတုသ်အသံ မချိတ်ဆက်ထားပါ"</string>
</resources>
diff --git a/res/values-my-rMM/strings_pbap_client.xml b/res/values-my-rMM/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-my-rMM/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 91eb03d..05e3821 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Godta"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Det oppstod et tidsavbrudd under mottak av fil fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-deling: Innkommende fil"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Vil du motta denne filen?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Innkommende fil fra en annen enhet. Bekreft at du ønsker å motta denne filen."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Innkommende fil"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til å sende <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Mottar <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: <xliff:g id="FILE">%1$s</xliff:g> er mottatt"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Filen <xliff:g id="FILE">%1$s</xliff:g> er ikke mottatt"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Tøm"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Lagre"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Avbryt"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Velg kontoene du vil dele via Bluetooth. Du må fortsatt godta eventuell tilgang til kontoene når du kobler til."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Velg kontoene du vil dele via Bluetooth. Du må fortsatt godta eventuell tilgang til kontoene når du kobler til."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Plasser igjen:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Appikon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Innstillinger for meldingsdeling via Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Kan ikke velge kontoen. Det er 0 plasser igjen"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-lyd er tilkoblet"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyd er frakoblet"</string>
</resources>
diff --git a/res/values-nb/strings_pbap_client.xml b/res/values-nb/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-nb/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index da97be3..8a8e677 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"स्वीकार्नुहोस्"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक छ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट आगमन फाइल स्वीकार्दा समय सकिएको थियो"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"ब्लुटुथ साझेदारी: आगमन फाइल"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"के तपाईँ यो फाइल प्राप्त गर्न चाहनु हुन्छ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"अर्को उपकरणबाट आगमन फाइल। यो फाइल तपाईँ प्राप्त गर्न चाहनु हुन्छ भन्ने कुरा निश्चित गर्नुहोस्।"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आगमन फाइल"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ले <xliff:g id="FILE">%2$s</xliff:g> पठाउन तयार छ"</string>
<string name="notification_receiving" msgid="4674648179652543984">"ब्लुटुथ साझेदारी:<xliff:g id="FILE">%1$s</xliff:g> प्राप्त गर्दै"</string>
<string name="notification_received" msgid="3324588019186687985">"ब्लुटुथ साझेदारी: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त भयो"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"ब्लुटुथ साझेदारी: फाइल: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त भएन"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"हटाउनुहोस्"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"सुरक्षित गर्नुहोस्"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द गर्नुहोस्"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"तपाईंले ब्लुटुथ मार्फत साझेदारी गर्न चाहेको खाता चयन गर्नुहोस्। तपाईंले अझै पनि खातामा जडान गर्दा कुनै पनि पहुँच स्वीकार गर्नुपर्छ।"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तपाईंले ब्लुटुथ मार्फत साझेदारी गर्न चाहेका खाताहरू चयन गर्नुहोस्। तपाईंले अझै पनि खाताहरूमा जडान गर्दा कुनै पनि पहुँच स्वीकार गर्नुपर्छ।"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"स्लटहरू बाँकी:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"अनुप्रयोग आइकन"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लुटुथ सन्देश साझेदारी सेटिङहरू"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाता चयन गर्न सक्दैन। ० स्लटहरू बाँकी"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ब्लुटुथ सम्बन्धी अडियो यन्त्रलाई जडान गरियो"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लुटुथ सम्बन्धी अडियो यन्त्रलाई विच्छेद गरियो"</string>
</resources>
diff --git a/res/values-ne-rNP/strings_pbap_client.xml b/res/values-ne-rNP/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ne-rNP/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 45d784b..21fff33 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepteren"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Er is een time-out opgetreden bij het accepteren van een inkomend bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\'"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Delen via Bluetooth: inkomend bestand"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Wilt je dit bestand ontvangen?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Er is een inkomend bestand van een ander apparaat beschikbaar. Bevestig dat je dit bestand wilt ontvangen."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkomend bestand"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is gereed om <xliff:g id="FILE">%2$s</xliff:g> te verzenden"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Delen via Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
<string name="notification_received" msgid="3324588019186687985">"Delen via Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Delen via Bluetooth: bestand <xliff:g id="FILE">%1$s</xliff:g> niet ontvangen"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Wissen"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Opslaan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuleren"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecteer de accounts die je wilt delen via Bluetooth. Je moet nog steeds elke toegang tot de accounts accepteren wanneer er verbinding wordt gemaakt."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecteer de accounts die je wilt delen via Bluetooth. Je moet nog steeds elke toegang tot de accounts accepteren wanneer er verbinding wordt gemaakt."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Plaatsen over:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"App-pictogram"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Instellingen voor delen van berichten via Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Kan account niet selecteren. 0 plaatsen over"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-audio gekoppeld"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-audio ontkoppeld"</string>
</resources>
diff --git a/res/values-nl/strings_pbap_client.xml b/res/values-nl/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-nl/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index d170e9f..febaf3e 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ਸਵੀਕਾਰ ਕਰੋ"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ਠੀਕ"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਇੱਕ ਇਨਕਮਿੰਗ ਫਾਈਲ ਸਵੀਕਾਰ ਕਰਨ ਵੇਲੇ ਇੱਕ ਟਾਈਮਆਊਟ ਹੋਇਆ ਸੀ।"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth ਸ਼ੇਅਰ: ਇਨਕਮਿੰਗ ਫਾਈਲ"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ਕੀ ਤੁਸੀਂ ਇਹ ਫਾਈਲ ਪ੍ਰਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ਦੂਜੀ ਡਿਵਾਈਸ ਤੋਂ ਇਨਕਮਿੰਗ ਫਾਈਲ। ਪੁਸ਼ਟੀ ਕਰੋ ਕਿ ਤੁਸੀਂ ਇਹ ਫਾਈਲ ਪ੍ਰਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ।"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ਇਨਕਮਿੰਗ ਫਾਈਲ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> ਭੇਜਣ ਲਈ ਤਿਆਰ ਹੈ"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth ਸ਼ੇਅਰ: <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਕਰ ਰਿਹਾ ਹੈ"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth ਸ਼ੇਅਰ: <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਕੀਤੀ"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth ਸ਼ੇਅਰ: ਫਾਈਲ <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਨਹੀਂ ਕੀਤੀ"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ਹਟਾਓ"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ਸੁਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ਰੱਦ ਕਰੋ"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"ਉਹ ਖਾਤੇ ਚੁਣੋ ਜਿਹਨਾਂ ਨੂੰ ਤੁਸੀਂ ਬਲੂਟੁੱਥ ਰਾਹੀਂ ਸ਼ੇਅਰ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ। ਤੁਹਾਨੂੰ ਤਦ ਵੀ ਕਨੈਕਟ ਕਰਨ ਵੇਲੇ ਖਾਤਿਆਂ ਤੱਕ ਕਿਸੇ ਵੀ ਪਹੁੰਚ ਨੂੰ ਸਵੀਕਾਰ ਕਰਨਾ ਪਵੇਗਾ।"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ਉਹਨਾਂ ਖਾਤਿਆਂ ਨੂੰ ਚੁਣੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਬਲੂਟੁੱਥ ਦੇ ਰਾਹੀਂ ਸਾਂਝਾ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ। ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕਨੈਕਟ ਕਰਨ ਦੌਰਾਨ ਖਾਤਿਆਂ \'ਤੇ ਕਿਸੇ ਵੀ ਪਹੁੰਚ ਨੂੰ ਸਵੀਕਾਰ ਕਰਨਾ ਹੋਵੇਗਾ।"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ਸਲੌਟ ਬਾਕੀ:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ਐਪਲੀਕੇਸ਼ਨ ਆਈਕਨ"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ਬਲੂਟੁੱਥ ਸੁਨੇਹਾ ਸ਼ੇਅਰਿੰਗ ਸੈੱਟਿੰਗਜ਼"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ਖਾਤਾ ਨਹੀਂ ਚੁਣ ਸਕਦਾ। 0 ਸਲੌਟ ਬਾਕੀ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
</resources>
diff --git a/res/values-pa-rIN/strings_pbap_client.xml b/res/values-pa-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-pa-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index ebab1ac..99ed7fe 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Akceptuj"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Upłynął czas oczekiwania przy akceptowaniu przychodzącego pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Udostępnianie Bluetooth: plik przychodzący"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Chcesz odebrać ten plik?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Przesłano plik z innego urządzenia. Potwierdź, że chcesz go odebrać."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Plik przychodzący"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> może wysłać plik <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Udostępnianie Bluetooth: odbieranie <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Udostępnianie Bluetooth: odebrano plik <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Udostępnianie Bluetooth: nie odebrano pliku <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Wyczyść"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Zapisz"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anuluj"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Wybierz konta, które chcesz udostępnić przez Bluetooth. Przy łączeniu trzeba zgodzić się na dostęp do kont."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Wybierz konta, które chcesz udostępnić przez Bluetooth. Wymagana jest zgoda na dostęp do kont."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Wolne miejsca:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikacji"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Ustawienia udostępniania wiadomości przez Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Nie można wybrać konta. Pozostało 0 miejsc."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Dźwięk Bluetooth podłączony"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Dźwięk Bluetooth odłączony"</string>
</resources>
diff --git a/res/values-pl/strings_pbap_client.xml b/res/values-pl/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-pl/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index f0fb7e8..c8290bd 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aceitar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Foi excedido o tempo limite durante a aceitação de um ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Partilha Bluetooth: ficheiro recebido"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Pretende receber este ficheiro?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"A receber ficheiro de outro aparelho. Confirme se pretende recebê-lo."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ficheiro a receber"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está pronto para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Partilha Bluetooth: a receber <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Partilha Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recebido"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Partilha Bluetooth: Ficheiro <xliff:g id="FILE">%1$s</xliff:g> não recebido"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Limpar"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecione as contas que pretende partilhar através de Bluetooth. Ao ligar, ainda tem de aceitar eventuais acessos às contas."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecione as contas que pretende partilhar através de Bluetooth. Ao ligar, ainda tem de aceitar eventuais acessos às contas."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Ranhuras restantes:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ícone de aplicação"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Definições de partilha de mensagens por Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Não é possível selecionar a conta. Não há ranhuras restantes"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Áudio Bluetooth ligado"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desligado"</string>
</resources>
diff --git a/res/values-pt-rPT/strings_pbap_client.xml b/res/values-pt-rPT/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-pt-rPT/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index cf7f34c..ebc2d6a 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Aceitar"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tempo limite excedido ao receber um arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Compartilhamento Bluetooth: arquivo recebido"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Deseja receber este arquivo?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Arquivo recebido de outro dispositivo. Confirme se deseja receber este arquivo."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Arquivo recebido"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está pronto para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Compart. Bluetooth: recebendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Compart. Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recebido"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Compart. Bluetooth: o arquivo <xliff:g id="FILE">%1$s</xliff:g> não foi recebido"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Limpar"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selecione as contas que você deseja compartilhar via Bluetooth. Você ainda precisa aceitar qualquer acesso às contas ao conectar."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecione as contas que você deseja compartilhar via Bluetooth. Você ainda precisa aceitar qualquer acesso às contas ao se conectar."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Espaços disponíveis:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ícone do app"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Configurações de compartilhamento de mensagens Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Não é possível selecionar conta. 0 espaços disponíveis"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Áudio Bluetooth conectado"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desconectado"</string>
</resources>
diff --git a/res/values-pt/strings_pbap_client.xml b/res/values-pt/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-pt/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 825db4d..9a4a536 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -19,12 +19,12 @@
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accesați managerul de descărcare."</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite aplicațiilor să acceseze managerul Distribuire prin Bluetooth și să-l utilizeze la transferul fișierelor."</string>
<string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Acces la dispozitivele Bluetooth din lista albă."</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite aplicației să treacă temporar pe lista albă un dispozitiv Bluetooth, permițându-i să trimită fişiere la acest dispozitiv fără confirmare din partea utilizatorului."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite aplicației să treacă temporar pe lista albă un dispozitiv Bluetooth, permițându-i să trimită fișiere la acest dispozitiv fără confirmare din partea utilizatorului."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
<string name="unknown_device" msgid="9221903979877041009">"Dispozitiv necunoscut"</string>
<string name="unknownNumber" msgid="4994750948072751566">"Necunoscut"</string>
<string name="airplane_error_title" msgid="2683839635115739939">"Mod Avion"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"Nu puteţi utiliza Bluetooth în modul Avion."</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Nu puteți utiliza Bluetooth în modul Avion."</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
<string name="bt_enable_line1" msgid="7203551583048149">"Pentru a putea utiliza serviciile Bluetooth, trebuie mai întâi să le activați."</string>
<string name="bt_enable_line2" msgid="4341936569415937994">"Activați acum Bluetooth?"</string>
@@ -32,43 +32,42 @@
<string name="bt_enable_ok" msgid="3432462749994538265">"Activați"</string>
<string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transfer de fișier"</string>
<string name="incoming_file_confirm_content" msgid="2752605552743148036">"Acceptați fișierul primit?"</string>
- <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Refuzaţi"</string>
- <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Acceptaţi"</string>
+ <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Refuzați"</string>
+ <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Acceptați"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"A fost atins timpul limită pentru acceptarea unui fișier primit de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: se primeşte fişierul"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Doriți să primiţi acest fișier?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Un fișier de la alt dispozitiv se află în curs de primire. Confirmați dacă doriți primirea acestuia."</string>
- <string name="notification_receiving" msgid="4674648179652543984">"Distribuire prin Bluetooth: se primeşte <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fișier primit"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> este gata să trimită <xliff:g id="FILE">%2$s</xliff:g>"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"Distribuire prin Bluetooth: se primește <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Distribuire prin Bluetooth: s-a primit <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="notification_received_fail" msgid="3619350997285714746">"Distribuire prin Bluetooth: fişierul <xliff:g id="FILE">%1$s</xliff:g> nu s-a primit"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"Distribuire prin Bluetooth: fișierul <xliff:g id="FILE">%1$s</xliff:g> nu s-a primit"</string>
<string name="notification_sending" msgid="3035748958534983833">"Distribuire prin Bluetooth: se trimite <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_sent" msgid="9218710861333027778">"Distribuire prin Bluetooth: s-a trimis <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_sent_complete" msgid="302943281067557969">"100 % finalizat"</string>
- <string name="notification_sent_fail" msgid="6696082233774569445">"Distribuire prin Bluetooth: fişierul <xliff:g id="FILE">%1$s</xliff:g> nu a fost trimis"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"Distribuire prin Bluetooth: fișierul <xliff:g id="FILE">%1$s</xliff:g> nu a fost trimis"</string>
<string name="download_title" msgid="3353228219772092586">"Transfer de fișier"</string>
<string name="download_line1" msgid="4926604799202134144">"De la: „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
<string name="download_line2" msgid="5876973543019417712">"Fișier: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="download_line3" msgid="4384821622908676061">"Dimensiunea fişierului: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="download_line3" msgid="4384821622908676061">"Dimensiunea fișierului: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="download_line4" msgid="8535996869722666525"></string>
- <string name="download_line5" msgid="3069560415845295386">"Se primeşte fişierul..."</string>
+ <string name="download_line5" msgid="3069560415845295386">"Se primește fișierul..."</string>
<string name="download_cancel" msgid="9177305996747500768">"Opriți"</string>
- <string name="download_ok" msgid="5000360731674466039">"Ascundeţi"</string>
+ <string name="download_ok" msgid="5000360731674466039">"Ascundeți"</string>
<string name="incoming_line1" msgid="2127419875681087545">"De la"</string>
<string name="incoming_line2" msgid="3348994249285315873">"Numele fișierului"</string>
<string name="incoming_line3" msgid="7954237069667474024">"Dimensiunea"</string>
- <string name="download_fail_line1" msgid="3846450148862894552">"Fişierul nu este primit"</string>
+ <string name="download_fail_line1" msgid="3846450148862894552">"Fișierul nu este primit"</string>
<string name="download_fail_line2" msgid="8950394574689971071">"Fișier: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="download_fail_line3" msgid="3451040656154861722">"Motiv: <xliff:g id="REASON">%1$s</xliff:g>"</string>
<string name="download_fail_ok" msgid="1521733664438320300">"OK"</string>
<string name="download_succ_line5" msgid="4509944688281573595">"Fișier primit"</string>
- <string name="download_succ_ok" msgid="7053688246357050216">"Deschideţi"</string>
+ <string name="download_succ_ok" msgid="7053688246357050216">"Deschideți"</string>
<string name="upload_line1" msgid="2055952074059709052">"Către: „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
<string name="upload_line3" msgid="4920689672457037437">"Tip de fișier: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
- <string name="upload_line5" msgid="7759322537674229752">"Se trimite fişierul..."</string>
+ <string name="upload_line5" msgid="7759322537674229752">"Se trimite fișierul..."</string>
<string name="upload_succ_line5" msgid="5687317197463383601">"Fișier trimis"</string>
<string name="upload_succ_ok" msgid="7705428476405478828">"OK"</string>
- <string name="upload_fail_line1" msgid="7899394672421491701">"Fişierul nu a fost trimis la „<xliff:g id="RECIPIENT">%1$s</xliff:g>”."</string>
+ <string name="upload_fail_line1" msgid="7899394672421491701">"Fișierul nu a fost trimis la „<xliff:g id="RECIPIENT">%1$s</xliff:g>”."</string>
<string name="upload_fail_line1_2" msgid="2108129204050841798">"Fișier: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="upload_fail_ok" msgid="5807702461606714296">"Încercați din nou"</string>
<string name="upload_fail_cancel" msgid="9118496285835687125">"Închideți"</string>
@@ -76,39 +75,39 @@
<string name="unknown_file" msgid="6092727753965095366">"Fișier necunoscut"</string>
<string name="unknown_file_desc" msgid="480434281415453287">"Nu există nicio aplicație care să gestioneze acest tip de fișier. \n"</string>
<string name="not_exist_file" msgid="3489434189599716133">"Niciun fișier"</string>
- <string name="not_exist_file_desc" msgid="4059531573790529229">"Fişierul nu există. \n"</string>
- <string name="enabling_progress_title" msgid="436157952334723406">"Aşteptaţi..."</string>
+ <string name="not_exist_file_desc" msgid="4059531573790529229">"Fișierul nu există. \n"</string>
+ <string name="enabling_progress_title" msgid="436157952334723406">"Așteptați..."</string>
<string name="enabling_progress_content" msgid="4601542238119927904">"Se activează Bluetooth..."</string>
- <string name="bt_toast_1" msgid="972182708034353383">"Se va primi fişierul. Verificaţi progresul în panoul de notificări."</string>
- <string name="bt_toast_2" msgid="8602553334099066582">"Fişierul nu poate fi primit."</string>
- <string name="bt_toast_3" msgid="6707884165086862518">"Primirea fişierului de la „<xliff:g id="SENDER">%1$s</xliff:g>” a fost anulată"</string>
- <string name="bt_toast_4" msgid="4678812947604395649">"Se trimite fişierul către „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
- <string name="bt_toast_5" msgid="2846870992823019494">"Se trimit <xliff:g id="NUMBER">%1$s</xliff:g> (de) fişiere către „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
- <string name="bt_toast_6" msgid="1855266596936622458">"Trimiterea fişierului către „<xliff:g id="RECIPIENT">%1$s</xliff:g>” a fost anulată"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nu există spaţiu suficient pe stocarea USB pentru a salva fişierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nu există suficient spaţiu pe cardul SD pentru a salva fişierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="bt_sm_2_2" msgid="2965243265852680543">"Spaţiu necesar: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="bt_toast_1" msgid="972182708034353383">"Se va primi fișierul. Verificați progresul în panoul de notificări."</string>
+ <string name="bt_toast_2" msgid="8602553334099066582">"Fișierul nu poate fi primit."</string>
+ <string name="bt_toast_3" msgid="6707884165086862518">"Primirea fișierului de la „<xliff:g id="SENDER">%1$s</xliff:g>” a fost anulată"</string>
+ <string name="bt_toast_4" msgid="4678812947604395649">"Se trimite fișierul către „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
+ <string name="bt_toast_5" msgid="2846870992823019494">"Se trimit <xliff:g id="NUMBER">%1$s</xliff:g> (de) fișiere către „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
+ <string name="bt_toast_6" msgid="1855266596936622458">"Trimiterea fișierului către „<xliff:g id="RECIPIENT">%1$s</xliff:g>” a fost anulată"</string>
+ <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nu există spațiu suficient pe stocarea USB pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+ <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nu există suficient spațiu pe cardul SD pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+ <string name="bt_sm_2_2" msgid="2965243265852680543">"Spațiu necesar: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Există prea multe solicitări în curs de procesare. Încercați din nou mai târziu."</string>
- <string name="status_pending" msgid="2503691772030877944">"Transferul fişierului nu a început încă."</string>
- <string name="status_running" msgid="6562808920311008696">"Transferul fişierului este în curs de desfăşurare."</string>
- <string name="status_success" msgid="239573225847565868">"Transferul fişierului s-a încheiat."</string>
- <string name="status_not_accept" msgid="1695082417193780738">"Conţinutul nu este acceptat."</string>
- <string name="status_forbidden" msgid="613956401054050725">"Transfer interzis de dispozitivul de destinaţie."</string>
+ <string name="status_pending" msgid="2503691772030877944">"Transferul fișierului nu a început încă."</string>
+ <string name="status_running" msgid="6562808920311008696">"Transferul fișierului este în curs de desfășurare."</string>
+ <string name="status_success" msgid="239573225847565868">"Transferul fișierului s-a încheiat."</string>
+ <string name="status_not_accept" msgid="1695082417193780738">"Conținutul nu este acceptat."</string>
+ <string name="status_forbidden" msgid="613956401054050725">"Transfer interzis de dispozitivul de destinație."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer anulat de către utilizator."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problemă de stocare."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nu există spaţiu de stocare USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nu există card SD. Introduceţi un card SD pentru a salva fişierele transferate."</string>
- <string name="status_connection_error" msgid="947681831523219891">"Conectare eşuată."</string>
+ <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nu există spațiu de stocare USB."</string>
+ <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nu există card SD. Introduceți un card SD pentru a salva fișierele transferate."</string>
+ <string name="status_connection_error" msgid="947681831523219891">"Conectare eșuată."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Solicitarea nu poate fi gestionată corect."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Eroare necunoscută."</string>
<string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth recepționat"</string>
<string name="download_success" msgid="7036160438766730871">"Primire finalizată: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
<string name="upload_success" msgid="4014469387779648949">"Trimitere finalizată: <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
<string name="inbound_history_title" msgid="6940914942271327563">"Transferuri de intrare"</string>
- <string name="outbound_history_title" msgid="4279418703178140526">"Transferuri de ieşire"</string>
+ <string name="outbound_history_title" msgid="4279418703178140526">"Transferuri de ieșire"</string>
<string name="no_transfers" msgid="3482965619151865672">"Istoricul de transferuri este gol."</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Toate elementele din listă vor fi eliminate."</string>
- <string name="outbound_noti_title" msgid="8051906709452260849">"Distribuire prin Bluetooth: fişiere trimise"</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"Distribuire prin Bluetooth: fișiere trimise"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth: fișiere primite"</string>
<plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
<item quantity="few"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> fișiere netransferate.</item>
@@ -120,15 +119,17 @@
<item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> de fișiere transferate, %2$s</item>
<item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> fișier transferat, %2$s</item>
</plurals>
- <string name="transfer_menu_clear_all" msgid="790017462957873132">"Ștergeţi lista"</string>
- <string name="transfer_menu_open" msgid="3368984869083107200">"Deschideţi"</string>
- <string name="transfer_menu_clear" msgid="5854038118831427492">"Ștergeţi din listă"</string>
- <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ștergeţi"</string>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"Ștergeți lista"</string>
+ <string name="transfer_menu_open" msgid="3368984869083107200">"Deschideți"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"Ștergeți din listă"</string>
+ <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ștergeți"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvați"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anulați"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Selectați conturile la care doriți să permiteți accesul prin Bluetooth. Va trebui să acceptați accesul la conturi la conectare."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selectați conturile la care doriți să permiteți accesul prin Bluetooth. Va trebui să acceptați accesul la conturi la conectare."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Sloturi rămase:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Pictograma aplicației"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Setări de permitere a accesului la mesajele prin Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Nu se poate selecta contul. 0 sloturi rămase"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audio prin Bluetooth conectat"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio prin Bluetooth deconectat"</string>
</resources>
diff --git a/res/values-ro/strings_pbap.xml b/res/values-ro/strings_pbap.xml
index cc84114..e5119f0 100644
--- a/res/values-ro/strings_pbap.xml
+++ b/res/values-ro/strings_pbap.xml
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Introduceţi cheia de sesiune pentru %1$s"</string>
+ <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Introduceți cheia de sesiune pentru %1$s"</string>
<string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Cheie de sesiune Bluetooth solicitată"</string>
<string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"A expirat timpul pentru acceptarea conexiunii cu %1$s"</string>
<string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"A expirat timpul de introducere a cheii de sesiune cu %1$s"</string>
<string name="auth_notif_ticker" msgid="1575825798053163744">"Cerere de autentificare pentru protocolul OBEX"</string>
<string name="auth_notif_title" msgid="7599854855681573258">"Cheia sesiunii"</string>
- <string name="auth_notif_message" msgid="6667218116427605038">"Introduceţi cheia de sesiune pentru %1$s"</string>
- <string name="defaultname" msgid="4821590500649090078">"Set de maşină"</string>
+ <string name="auth_notif_message" msgid="6667218116427605038">"Introduceți cheia de sesiune pentru %1$s"</string>
+ <string name="defaultname" msgid="4821590500649090078">"Set de mașină"</string>
<string name="unknownName" msgid="2841414754740600042">"Nume necunoscut"</string>
<string name="localPhoneName" msgid="2349001318925409159">"Numele meu"</string>
<string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
diff --git a/res/values-ro/strings_pbap_client.xml b/res/values-ro/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ro/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ro/test_strings.xml b/res/values-ro/test_strings.xml
index 773db8d..4221e93 100644
--- a/res/values-ro/test_strings.xml
+++ b/res/values-ro/test_strings.xml
@@ -6,9 +6,9 @@
<string name="insert_record" msgid="1450997173838378132">"Inserați o înregistrare"</string>
<string name="update_record" msgid="2480425402384910635">"Confirmați înregistrarea"</string>
<string name="ack_record" msgid="6716152390978472184">"Înregistrare Ack"</string>
- <string name="deleteAll_record" msgid="4383349788485210582">"Ștergeţi toate înregistrările"</string>
+ <string name="deleteAll_record" msgid="4383349788485210582">"Ștergeți toate înregistrările"</string>
<string name="ok_button" msgid="6519033415223065454">"OK"</string>
- <string name="delete_record" msgid="4645040331967533724">"Ștergeţi înregistrarea"</string>
- <string name="start_server" msgid="9034821924409165795">"Porniţi serverul TCP"</string>
- <string name="notify_server" msgid="4369106744022969655">"Notificaţi serverul TCP"</string>
+ <string name="delete_record" msgid="4645040331967533724">"Ștergeți înregistrarea"</string>
+ <string name="start_server" msgid="9034821924409165795">"Porniți serverul TCP"</string>
+ <string name="notify_server" msgid="4369106744022969655">"Notificați serverul TCP"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index a99f4f9..cb673e6 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Принять"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ОК"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"В процессе приема файла от \"<xliff:g id="SENDER">%1$s</xliff:g>\" произошел тайм-аут"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Передача по Bluetooth"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Принять файл?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Входящий файл с другого устройства. Подтвердите, что хотите загрузить его."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Входящий файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Устройство \"<xliff:g id="SENDER">%1$s</xliff:g>\" готово к отправке файла \"<xliff:g id="FILE">%2$s</xliff:g>\""</string>
<string name="notification_receiving" msgid="4674648179652543984">"Передача по Bluetooth: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Получено по Bluetooth: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Файл не передан: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Очистить"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сохранить"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Отменить"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Выберите аккаунты, к которым нужно открыть доступ через Bluetooth. Доступ необходимо подтверждать при каждом подключении."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Выберите аккаунты, к которым нужно открыть доступ через Bluetooth. Доступ необходимо подтверждать при каждом подключении."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Осталось мест:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Значок приложения"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Настройки доступа к сообщениям через Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Не удалось выбрать аккаунт: не осталось мест."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Звук через Bluetooth включен"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Звук через Bluetooth отключен"</string>
</resources>
diff --git a/res/values-ru/strings_pbap_client.xml b/res/values-ru/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ru/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 2edcc43..f87cc9e 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"පිළිගන්න"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"හරි"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් පැමිණෙන ගොනුවක් පිළිගන්නා අතරතුර කාල නිමාවක් විය"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"බ්ලූටූත් බෙදා ගැනීම: පැමිණෙන ගොනුව"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ඔබට මෙම ගොනුව ලබා ගැනීමට අවශ්යද?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"වෙනත් උපාංගයක් වෙතින් පැමිණෙන ගොනුව. මෙම ගොනුව ලබා ගැනීමට ඔබට අවශ්ය බව තහවුරු කරන්න."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ලැබෙන ගොනුව"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> යැවීමට සූදානම්ය"</string>
<string name="notification_receiving" msgid="4674648179652543984">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ලැබේ"</string>
<string name="notification_received" msgid="3324588019186687985">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ලැබිණි"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ගොනුව නොලැබිණි"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"හිස් කරන්න"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"සුරකින්න"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"අවලංගු කරන්න"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"බ්ලූටූත් හරහා ඔබට බෙදාගැනීමට අවශ්ය ගිණුම් තෝරන්න. සම්බන්ධ වන විට ගිණුම් වෙත ඕනෑම ප්රවේශයක් ඔබ තවමත් පිළිගත යුතුයි."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ඔබට බ්ලූටූත් හරහා බෙදා ගැනීමට අවශ්ය ගිණුම් තෝරන්න. සම්බන්ධ වන විට ඔබට තවම ගිණුම් වෙත ඕනෑම ප්රවේශයක් පිළිගැනීමට සිදු වේ."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ඉතිරිව ඇති විවර:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"යෙදුම් නිරූපකය"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"බ්ලූටූත් පණිවිඩ බෙදාගැනීමේ සැකසීම්"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ගිණුමක් තේරිය නොහැක. විවර 0 ක් ඉතිරිව තිබේ"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"බ්ලූටූත් ශ්රව්යය සම්බන්ධ කරන ලදී"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"බ්ලූටූත් ශ්රව්යය විසන්ධි කරන ලදී"</string>
</resources>
diff --git a/res/values-si-rLK/strings_pbap_client.xml b/res/values-si-rLK/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-si-rLK/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 5550234..889f379 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Prijať"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Pri prijímaní prichádzajúceho súboru od používateľa <xliff:g id="SENDER">%1$s</xliff:g> vypršal časový limit."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: Prichádzajúci súbor"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Chcete prijať tento súbor?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Potvrďte príjem súboru prichádzajúceho z iného zariadenia."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Prichádzajúci súbor"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> je pripravený/-á odoslať súbor <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: Prijíma sa <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> prijatý"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> neprijatý"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vymazať"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Uložiť"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Zrušiť"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Vyberte účty, ktoré chcete zdieľať prostredníctvom rozhrania Bluetooth. Počas pripájania budete musieť aj tak prijať akékoľvek prístupy k účtom."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vyberte účty, ktoré chcete zdieľať prostredníctvom rozhrania Bluetooth. Počas pripájania budete musieť aj tak prijať akékoľvek prístupy k účtom."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Počet zostávajúcich slotov:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikácie"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Nastavenia zdieľania správ prostredníctvom rozhrania Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Účet sa nedá vybrať. Nie sú k dispozícii žiadne ďalšie sloty."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Rozhranie Bluetooth Audio je pripojené"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Rozhranie Bluetooth Audio je odpojené"</string>
</resources>
diff --git a/res/values-sk/strings_pbap_client.xml b/res/values-sk/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sk/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 1d5f9d3..2fcd223 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Sprejmi"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"V redu"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Pri sprejemanju datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>« je potekla časovna omejitev"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth: Prihajajoča datoteka"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Ali želite prejeti to datoteko?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Iz druge naprave prihaja datoteka. Potrdite, da jo želite prejeti."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dohodna datoteka"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Uporabnik <xliff:g id="SENDER">%1$s</xliff:g> je pripravljen za pošiljanje datoteke <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: Prejemanje <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth: Prejeto <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: Datoteka <xliff:g id="FILE">%1$s</xliff:g> ni bila prejeta"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Počisti"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Shrani"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Prekliči"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Izberite račune, ki jih želite dati v skupno rabo prek Bluetootha. Pri vzpostavljanju povezave morate še vedno sprejeti morebiten dostop do računov."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Izberite račune, ki jih želite dati v skupno rabo prek Bluetootha. Pri vzpostavljanju povezave morate še vedno sprejeti morebiten dostop do računov."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Preostala mesta:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona aplikacije"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Nastavitve skupne rabe sporočil prek Bluetootha"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Ni mogoče izbrati računa. Na voljo je 0 mest."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Zvok prek Bluetootha je povezan"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Zvok prek Bluetootha ni povezan"</string>
</resources>
diff --git a/res/values-sl/strings_pbap_client.xml b/res/values-sl/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sl/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 1c4e732..5e3c34a 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Prano"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Në rregull!"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Përfundoi koha e veprimit për pranimin e skedarit hyrës nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Shpërndarja përmes bluetooth-it: Skedar hyrës"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Dëshiron ta marrësh këtë skedar?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Skedar hyrës nga një tjetër pajisje. Konfirmo që dëshiron ta marrësh këtë skedar."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Skedari në ardhje"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> është gati për të dërguar <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Shpërndarja përmes bluetooth-it: Po merret skedari <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Shpërndarja përmes bluetooth-it: U pranua skedari <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Shpërndarja përmes bluetooth-it: Skedari <xliff:g id="FILE">%1$s</xliff:g> nuk u pranua"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Pastro"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Ruaj"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anulo"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Zgjidh llogaritë që dëshiron të ndash nëpërmjet Bluetooth-it. Duhet të pranosh përsëri çdo qasje te llogaritë kur të lidhesh."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Zgjidh llogaritë që dëshiron të ndash me Bluetooth. Duhet të pranosh përsëri çdo qasje te llogaritë kur të lidhesh."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Fole të mbetura:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ikona e aplikacionit"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cilësimet e ndarjes së mesazheve me Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Llogaria nuk mund të zgjidhet. 0 fole të mbetura"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Audioja e \"bluetooth-it\" e lidhur"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audioja e \"bluetooth-it\" e shkëputur"</string>
</resources>
diff --git a/res/values-sq-rAL/strings_pbap_client.xml b/res/values-sq-rAL/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sq-rAL/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index d873a8f..03fed95 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Прихвати"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Потврди"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Дошло је до временског ограничења током пријема долазне датотеке од „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Дељење преко Bluetooth-а: долазећа датотека"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Да ли желите да примите ову датотеку?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Долазна датотека са другог уређаја. Потврдите да желите да је примите."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Долазна датотека"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> је спреман/на да пошаље <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth дељење: пријем <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth дељење: примљено <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth дељење: датотека <xliff:g id="FILE">%1$s</xliff:g> није примљена"</string>
@@ -126,9 +125,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Брисање"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сачувај"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Откажи"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Изаберите налоге које желите да делите преко Bluetooth-а. И даље морате да прихватите било какав приступ налозима при повезивању."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Изаберите налоге које желите да делите преко Bluetooth-а. И даље морате да прихватите било какав приступ налозима при повезивању."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Преосталих места:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Икона апликације"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Подешавања Bluetooth дељења порука"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Није могуће изабрати налог. Нема преосталих места"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth аудио је повезан"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Веза са Bluetooth аудијом је прекинута"</string>
</resources>
diff --git a/res/values-sr/strings_pbap_client.xml b/res/values-sr/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sr/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 44db0c8..35a9097 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Godkänn"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tidsgränsen överskreds när en inkommande fil från <xliff:g id="SENDER">%1$s</xliff:g> skulle tas emot"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth-delning: inkommande fil"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Vill du ta emot den här filen?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Inkommande fil från en annan enhet. Bekräfta att du vill ta emot denna fil."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkommande fil"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> är klar att skicka <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-delning: tar emot <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-delning: <xliff:g id="FILE">%1$s</xliff:g> har tagits emot"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-delning: filen <xliff:g id="FILE">%1$s</xliff:g> har inte tagits emot"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Rensa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Spara"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Avbryt"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Välj de konton du vill dela via Bluetooth. Du måste fortfarande godkänna åtkomsten till kontona vid anslutning."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Välj de konton du vill dela via Bluetooth. Du måste fortfarande godkänna åtkomsten till kontona vid anslutning."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Platser kvar:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Appikon"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Inställningar för meddelandedelning via Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Det gick inte att välja konton. 0 platser kvar"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-ljud är anslutet"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ljud är frånkopplat"</string>
</resources>
diff --git a/res/values-sv/strings_pbap_client.xml b/res/values-sv/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sv/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index bb744ca..0deefec 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Kubali"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Sawa"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Muda ulikatika wakati wa kukubali faili inayoingia kutoka \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Shiriki Bluetooth: Faili inayoingia"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Je, unataka kupokea faili hii?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Faili zinazoingia kutoka kwa kifaa kingine. Thibitisha kuwa unataka kupokea faili hii."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Faili Zinazoingia"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ako tayari kutuma <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Kushiriki kwa bluetooth: Inapokea <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Kushiriki kwa bluetooth: Imepokea <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Kushiriki kwa Bluetooth: Faili <xliff:g id="FILE">%1$s</xliff:g> haijapokewa"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Futa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Hifadhi"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ghairi"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Chagua akaunti unazotaka kushiriki kupitia Bluetooth. Bado unatakiwa kukubali ufikiaji wowote kwenye akaunti yako wakati unaunganisha."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chagua akaunti unazotaka kushiriki kupitia Bluetooth. Bado unatakiwa kutoa idhini ya ufikiaji wowote kwenye akaunti yako wakati unaunganisha."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Nafasi zilizosalia:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Aikoni ya programu"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Mipangilio ya Kushiriki Ujumbe wa Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Haiwezi kuchagua akaunti. Nafasi 0 zimesalia"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Imeunganisha sauti ya Bluetooth"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Imeondoa sauti ya Bluetooth"</string>
</resources>
diff --git a/res/values-sw/strings_pbap_client.xml b/res/values-sw/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-sw/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 98b284e..04f705e 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ஏற்கிறேன்"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"சரி"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" இடமிருந்து வரும் கோப்பை ஏற்கும்போது நேரம் முடிந்தது"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"புளூடூத் பகிர்வு: உள்வரும் கோப்பு"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"கோப்பைப் பெறவா?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"வேறொரு சாதனத்திலிருந்து கோப்பு வருகிறது. அதைப் பெற விருப்பமா என்பதை உறுதிப்படுத்தவும்."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"உள்வரும் கோப்பு"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> கோப்பை அனுப்புவதற்குத் தயாராக உள்ளார்"</string>
<string name="notification_receiving" msgid="4674648179652543984">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> ஐப் பெறுகிறது"</string>
<string name="notification_received" msgid="3324588019186687985">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> பெறப்பட்டது"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> ஐப் பெறவில்லை"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"அழி"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"சேமி"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ரத்துசெய்"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"புளுடூத் வழியாகப் பகிர விரும்பும் மின்னஞ்சல் கணக்குகளைத் தேர்வுசெய்யவும். இணைக்கும்போது கணக்கிற்கான எந்த அணுகல்களையும் ஏற்க வேண்டும்."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"புளூடூத் வழியாகப் பகிர விரும்பும் கணக்குகளைத் தேர்ந்தெடுக்கவும். இணைக்கும் போது கணக்குகளுக்கான அணுகலை மீண்டும் ஏற்க வேண்டும்."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"மீதமுள்ள ஸ்லாட்கள்:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"பயன்பாட்டு ஐகான்"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"புளூடூத் செய்தி பகிர்தல் அமைப்புகள்"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"கணக்கைத் தேர்வுசெய்ய முடியாது. ஸ்லாட்கள் எதுவுமில்லை"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"புளூடூத் ஆடியோ இணைக்கப்பட்டது"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"புளூடூத் ஆடியோ துண்டிக்கப்பட்டது"</string>
</resources>
diff --git a/res/values-ta-rIN/strings_pbap_client.xml b/res/values-ta-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ta-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 088f92f..e0725a4 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"అంగీకరిస్తున్నాను"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"సరే"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన ఇన్కమింగ్ ఫైల్ను అంగీకరిస్తున్నప్పుడు గడువు సమయం ముగిసింది"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"బ్లూటూత్ భాగస్వామ్యం: ఇన్కమింగ్ ఫైల్"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"మీరు ఈ ఫైల్ను స్వీకరించాలనుకుంటున్నారా?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"మరో పరికరం నుండి ఇన్కమింగ్ ఫైల్. మీరు ఈ ఫైల్ను స్వీకరించాలనుకుంటున్నట్లు నిర్ధారించండి."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ఫైల్ స్వీకరణ"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> ఫైల్ పంపడానికి సిద్ధంగా ఉన్నారు"</string>
<string name="notification_receiving" msgid="4674648179652543984">"బ్లూటూత్ భాగస్వామ్యం: <xliff:g id="FILE">%1$s</xliff:g>ను స్వీకరిస్తోంది"</string>
<string name="notification_received" msgid="3324588019186687985">"బ్లూటూత్ భాగస్వామ్యం: <xliff:g id="FILE">%1$s</xliff:g> స్వీకరించబడింది"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"బ్లూటూత్ భాగస్వామ్యం: <xliff:g id="FILE">%1$s</xliff:g> ఫైల్ స్వీకరించబడలేదు"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"క్లియర్ చేయి"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"సేవ్ చేయి"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"రద్దు చేయి"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"మీరు బ్లూటూత్ ద్వారా భాగస్వామ్యం చేయాలనుకునే ఖాతాలను ఎంచుకోండి. మీరు ఇప్పటికీ కనెక్ట్ చేస్తున్నప్పుడు ఖాతాలకు అందించే ఏ ప్రాప్యతనైనా ఆమోదించాలి."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"మీరు బ్లూటూత్ ద్వారా భాగస్వామ్యం చేయాలనుకునే ఖాతాలను ఎంచుకోండి. మీరు ఇప్పటికీ కనెక్ట్ చేస్తున్నప్పుడు ఖాతాలకు అందించే ఏ ప్రాప్యతనైనా ఆమోదించాల్సి ఉంటుంది."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"మిగిలిన స్లాట్లు:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"అనువర్తన చిహ్నం"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"బ్లూటూత్ సందేశ భాగస్వామ్య సెట్టింగ్లు"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ఖాతాను ఎంచుకోవడం సాధ్యపడదు. 0 స్లాట్లు మిగిలి ఉన్నాయి"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"బ్లూటూత్ ఆడియో కనెక్ట్ చేయబడింది"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"బ్లూటూత్ ఆడియో డిస్కనెక్ట్ చేయబడింది"</string>
</resources>
diff --git a/res/values-te-rIN/strings_pbap_client.xml b/res/values-te-rIN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-te-rIN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 1fe1795..7740718 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ยอมรับ"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ตกลง"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"มีการหมดเวลาเกิดขึ้นขณะยอมรับไฟล์ขาเข้าจาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"การแชร์ทางบลูทูธ: ไฟล์ขาเข้า"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"คุณต้องการรับไฟล์นี้หรือไม่"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ไฟล์ขาเข้าจากอุปกรณ์อื่น ยืนยันว่าคุณต้องการรับไฟล์นี้"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ไฟล์ที่เข้ามา"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> พร้อมที่จะส่ง <xliff:g id="FILE">%2$s</xliff:g> แล้ว"</string>
<string name="notification_receiving" msgid="4674648179652543984">"การแชร์ทางบลูทูธ: กำลังรับ <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"การแชร์ทางบลูทูธ: รับ <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"การแชร์ทางบลูทูธ: ไม่ได้รับไฟล์ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ล้าง"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"บันทึก"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ยกเลิก"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"เลือกบัญชีที่คุณต้องการแชร์ผ่านบลูทูธ คุณยังคงต้องยอมรับการเข้าถึงบัญชีทั้งหมดเมื่อเชื่อมต่อ"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"เลือกบัญชีที่คุณต้องการแชร์ผ่านบลูทูธ คุณยังคงต้องยอมรับการเข้าถึงบัญชีทั้งหมดเมื่อเชื่อมต่อ"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ช่องที่เหลือ:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ไอคอนแอปพลิเคชัน"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"การตั้งค่าการแชร์ข้อความผ่านบลูทูธ"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ไม่สามารถเลือกบัญชีได้ ช่องเหลือ 0 ช่อง"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"เชื่อมต่อ Bluetooth Audio แล้ว"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"ยกเลิกการเชื่อมต่อ Bluetooth Audio แล้ว"</string>
</resources>
diff --git a/res/values-th/strings_pbap_client.xml b/res/values-th/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-th/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 7033d1c..0959e88 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Tanggapin"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Nagkaroon ng timeout habang tinatanggap ang papasok na file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth share: Paparating na file"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Nais mo bang tanggapin ang file na ito?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Papasok na file mula sa isa pang device. Kumpirmahing nais mong tanggapin ang file na ito."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Papasok na file"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Handa nang ipadala ni <xliff:g id="SENDER">%1$s</xliff:g> ang <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Pagbahagi sa Bluetooth: Tinatanggap ang <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Pagbahagi sa Bluetooth: Natanggap ang <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Pagbahagi sa Bluetooth: Hindi natanggap ang file na <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"I-clear"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"I-save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Kanselahin"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Piliin ang mga account na gusto mong ibahagi sa pamamagitan ng Bluetooth. Kailangan mo pa ring tumanggap ng access sa mga account kapag kumokonekta."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Piliin ang mga account na gusto mong ibahagi sa pamamagitan ng Bluetooth. Kailangan mo pa ring tanggapin ang anumang pag-access sa mga account kapag kumokonekta."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Mga slot na natitira:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Icon ng Application"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Mga Setting ng Pagbabahagi ng Mensahe sa Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Hindi mapili ang account. 0 slot ang natitira"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Nakakonekta ang Bluetooth audio"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Nadiskonekta ang Bluetooth audio"</string>
</resources>
diff --git a/res/values-tl/strings_pbap_client.xml b/res/values-tl/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-tl/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index f539c97..2dc2726 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Kabul Et"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Tamam"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kabul etme süresi doldu"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth paylaşımı: Gelen dosya"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Bu dosyayı almak istiyor musunuz?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Başka cihazdan gelen bir dosya. Bu dosyayı almak istediğinizi onaylayın."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gelen dosya"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> adlı dosyayı göndermeye hazır"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> alınıyor"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> alındı"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> dosyası alınamadı"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Temizle"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Kaydet"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"İptal"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth üzerinden paylaşmak istediğiniz hesapları seçin. Bağlanırken yine de hesaplara erişimi kabul etmeniz gerekmektedir."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth üzerinden paylaşmak istediğiniz hesapları seçin. Bağlanırken yine de hesaplara erişimi kabul etmeniz gerekmektedir."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Kalan yuva sayısı:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Uygulama Simgesi"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth İleti Paylaşımı Ayarları"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Hesap seçilemiyor. 0 yuva kaldı."</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth ses bağlandı"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ses bağlantısı kesildi"</string>
</resources>
diff --git a/res/values-tr/strings_pbap_client.xml b/res/values-tr/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-tr/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b951c4d..8cac913 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Прийняти"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Під час приймання вхідного файлу від \"<xliff:g id="SENDER">%1$s</xliff:g>\" виникла затримка"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Через Bluetooth: вхідний файл"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Отримати цей файл?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Вхідний файл з іншого пристрою. Підтвердьте, що хочете отримати цей файл."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Вхідний файл"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Користувач <xliff:g id="SENDER">%1$s</xliff:g> готовий надіслати файл <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Через Bluetooth: отримання <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Через Bluetooth: отримано <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Через Bluetooth: файл <xliff:g id="FILE">%1$s</xliff:g> не отримано"</string>
@@ -128,9 +127,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Очистити"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Зберегти"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Скасувати"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Виберіть облікові записи, до яких ви хочете надати доступ через Bluetooth. Під час підключення все одно потрібно буде схвалити доступ до облікових записів."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Виберіть облікові записи, до яких ви хочете надати доступ через Bluetooth. Під час підключення все одно потрібно буде схвалити доступ до облікових записів."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Залишилося місць:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Значок додатка"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Параметри надсилання повідомлень через Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Обліковий запис не вибрано. Залишилося 0 місць"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Аудіо Bluetooth під’єднано"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудіо Bluetooth від’єднано"</string>
</resources>
diff --git a/res/values-uk/strings_pbap_client.xml b/res/values-uk/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-uk/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index fbf2136..2c83ec2 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"قبول کریں"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ٹھیک ہے"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے ایک موصول ہونے والی فائل کو قبول کرتے وقت ایک ٹائم آؤٹ پیش آگیا"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"بلوٹوتھ اشتراک: موصول ہونے والی فائل"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"کیا آپ اس فائل کو موصول کرنا چاہتے ہیں؟"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"کسی دوسرے آلہ سے موصول ہونے والی فائل۔ توثیق کریں کہ آپ اس فائل کو موصول کرنا چاہتے ہیں۔"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"آنے والی فائل"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> بھیجنے کے لئے تیار ہے"</string>
<string name="notification_receiving" msgid="4674648179652543984">"بلوٹوتھ اشتراک: <xliff:g id="FILE">%1$s</xliff:g> موصول ہو رہی ہے"</string>
<string name="notification_received" msgid="3324588019186687985">"بلوٹوتھ اشتراک: <xliff:g id="FILE">%1$s</xliff:g> موصول ہوئی"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"بلوٹوتھ اشتراک: فائل <xliff:g id="FILE">%1$s</xliff:g> موصول نہیں ہوئی"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"صاف کریں"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"محفوظ کریں"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"منسوخ کریں"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"ان اکاؤنٹس کو منتخب کریں جن کا آپ بلوٹوتھ کے ذریعے اشتراک کرنا چاہتے ہیں۔ آپ کو ابھی بھی منسلک ہوتے وقت اکاؤنٹس تک کسی رسائی کو قبول کرنا ہوگا۔"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ان اکاؤنٹس کو منتخب کریں جن کا آپ بلوٹوتھ کے ذریعے اشتراک کرنا چاہتے ہیں۔ آپ کو ابھی بھی منسلک ہوتے وقت اکاؤنٹس تک کسی بھی رسائی کو قبول کرنا ہوگا۔"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"بچے ہوئے سلاٹس:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ایپلیکیشن کا آئیکن"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"بلوٹوتھ پیغام کے اشتراک کی ترتیبات"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"اکاؤنٹ کو منتخب نہیں کیا جا سکتا ہے۔ 0 سلاٹس باقی ہیں"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"بلوٹوتھ آڈیو منسلک ہے"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"بلوٹوتھ آڈیو غیر منسلک ہے"</string>
</resources>
diff --git a/res/values-ur-rPK/strings_pbap_client.xml b/res/values-ur-rPK/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-ur-rPK/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 040aa55..a06b003 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Qabul qilish"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"dan kiruvchi xabarni olishga rozilik bildirilayotganda, kutish vaqti o‘tib ketdi."</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Bluetooth o‘tkazmalari"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Fayl qabul qilinsinmi?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Boshqa qurilmadan kiruvchi fayl. Ushbu faylni qabul qilmoqchi bo‘lsangiz, tasdiqlang."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Kiruvchi fayl"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"“<xliff:g id="SENDER">%1$s</xliff:g>” qurilmasi “<xliff:g id="FILE">%2$s</xliff:g>” faylini yuborishga tayyor"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth orqali yuborildi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth orqali olindi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Fayl qabul qilinmadi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Tozalash"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Saqlash"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Bekor qilish"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Bluetooth orqali o‘tkazmoqchi bo‘lgan hisoblarni tanlang. Har safar ulanishda so‘rovni tasdiqlash talab qilinadi."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth orqali narsa o‘tkazmoqchi bo‘lgan hisoblarni tanlang. Har safar ulanishda so‘rovni tasdiqlash talab qilinadi."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Qolgan joylar:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Ilova ikonkasi"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth orqali xabar ulashish sozlamalari"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Hisobni tanlab bo‘lmadi: bo‘sh joy qolmadi"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio ulandi"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio uzildi"</string>
</resources>
diff --git a/res/values-uz-rUZ/strings_pbap_client.xml b/res/values-uz-rUZ/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-uz-rUZ/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index e34224e..1e12eb0 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Chấp nhận"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Có thời gian chờ trong khi chấp nhận tệp tới từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Chia sẻ qua Bluetooth: Tệp đến"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Bạn có muốn nhận tệp này không?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Có tệp đến từ một thiết bị khác. Hãy xác nhận bạn muốn nhận tệp này."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Tệp đến"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> đã sẵn sàng gửi <xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Chia sẻ qua Bluetooth: Đang nhận <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Chia sẻ qua Bluetooth: Đã nhận <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Chia sẻ qua Bluetooth: Chưa nhận được tệp <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Xóa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Lưu"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Hủy"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Số khe cắm còn lại:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Biểu tượng ứng dụng"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt chia sẻ thư qua Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Không chọn được tài khoản. Còn lại 0 khe cắm"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Đã kết nối âm thanh Bluetooth"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Đã ngắt kết nối âm thanh Bluetooth"</string>
</resources>
diff --git a/res/values-vi/strings_pbap_client.xml b/res/values-vi/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-vi/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d9f5a1f..5e81da6 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"接受"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"确定"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接受来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件时发生超时"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"蓝牙共享:传入文件"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"要接收该文件吗?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"从其他设备传入了文件,请确认是否要接收。"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"有人发送文件给您"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>已准备好发送<xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"蓝牙共享:正在接收“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
<string name="notification_received" msgid="3324588019186687985">"蓝牙共享:已接收“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"蓝牙共享:未收到文件“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"清除"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"保存"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"取消"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"选中您要通过蓝牙共享的帐号。建立连接后,您仍需接受对这些帐号的所有访问请求。"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"选择您要通过蓝牙共享的帐号。连接时,您仍必须接受所有帐号访问请求。"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"剩余空档数:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"应用图标"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"蓝牙消息共享设置"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"无法选择帐号,目前没有任何空档"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"蓝牙音频已连接"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
</resources>
diff --git a/res/values-zh-rCN/strings_pbap_client.xml b/res/values-zh-rCN/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-zh-rCN/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 666b7dc..b3274c8 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"接受"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"確定"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接收來自「<xliff:g id="SENDER">%1$s</xliff:g>」的檔案時發生作業逾時"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"藍牙分享:外來檔案"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"您要接收這個檔案嗎?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"來自另一部裝置的檔案,請確認您要接收這個外來檔案。"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"收到的檔案"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>準備傳送<xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"藍牙分享:正在接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"藍牙分享:已接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"藍牙分享:未收到檔案 <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"清除"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"儲存"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"取消"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"選取您要透過藍牙分享的帳戶。連線時,您仍然必須接受所有帳戶存取要求。"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"選取您要透過藍牙分享的帳戶。連線時,您仍然必須接受所有帳戶存取要求。"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"剩餘插槽數:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"應用程式圖示"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"藍牙訊息分享設定"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"無法選取帳戶 (剩餘插槽數為 0)"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"已與藍牙音訊連接"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"已與藍牙音訊解除連接"</string>
</resources>
diff --git a/res/values-zh-rHK/strings_pbap_client.xml b/res/values-zh-rHK/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-zh-rHK/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 54d4052..2d12036 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"接受"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"確定"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接收來自「<xliff:g id="SENDER">%1$s</xliff:g>」的檔案時發生作業逾時"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"藍牙分享:外來檔案"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"您要接收這個檔案嗎?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"其他裝置傳來檔案,請確認是否要接收這個檔案。"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"有人傳送檔案給您"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>已準備好傳送<xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"藍牙分享:正在接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"藍牙分享:已接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"藍牙分享:未收到檔案 <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"清除"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"儲存"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"取消"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"選取您要透過藍牙分享的帳戶。連線時,您仍須接受所有帳戶存取要求。"</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"選取您要透過藍牙分享的帳戶。連線時,您仍須接受所有帳戶存取要求。"</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"剩餘插槽數:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"應用程式圖示"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"藍牙郵件分享設定"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"無法選取帳戶 (剩餘插槽數為 0)"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"已與藍牙音訊連線"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
</resources>
diff --git a/res/values-zh-rTW/strings_pbap_client.xml b/res/values-zh-rTW/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-zh-rTW/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 04b7936..20b4936 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -36,9 +36,8 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Yamukela"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Kulungile"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Isikhathi siphelile ngenkathi yamukela ifayela engenayo esuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Abelana ne-Bluetooth: Ifayela engenayo"</string>
- <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"Ngabe uyafuna ukuthola leli fayela?"</string>
- <string name="incoming_file_toast_msg" msgid="1733710749992901811">"Kunefayela engenayo esuka kwenye idivayisi. Qiniseka ukuthi ufuna ukuthola lefayela."</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ifayela elingenayo"</string>
+ <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ulungele ukuthumela i-<xliff:g id="FILE">%2$s</xliff:g>"</string>
<string name="notification_receiving" msgid="4674648179652543984">"Abelana ne-Bluetooth: Ithola<xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received" msgid="3324588019186687985">"Abelana ne-Bluetooth: Itholakele <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="notification_received_fail" msgid="3619350997285714746">"Abelana ne-Bluetooth: Ifayela <xliff:g id="FILE">%1$s</xliff:g> ayitholakalanga"</string>
@@ -124,9 +123,11 @@
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Sula"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Londoloza"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Khansela"</string>
- <string name="bluetooth_map_settings_intro" msgid="6793938602201480648">"Khetha ama-akhawunti ofuna ukwabelana ngawo nge-Bluetooth. Kusazomele wamukele noma yikuphi ukufinyelela kuma-akhawunti uma uxhumeka."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Khetha ama-akhawunti ofuna ukwabelana nawo nge-Bluetooth. Kusazomele wamukele noma yikuphi ukufinyelelwa kuma-akhawunti uma kuxhunywa."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Izikhala ezisele:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Isithonjana sohlelo lokusebenza"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Izilungiselelo zokwabelana ngomlayezo we-Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Ayikwazi ukukhetha i-akhawunti. 0 izikhala ezisele"</string>
+ <string name="bluetooth_connected" msgid="6718623220072656906">"Umsindo we-Bluetooth uxhunyiwe"</string>
+ <string name="bluetooth_disconnected" msgid="3318303728981478873">"Umsindo we-Bluetooth unqanyuliwe"</string>
</resources>
diff --git a/res/values-zu/strings_pbap_client.xml b/res/values-zu/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-zu/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 42ab4b9..277a01e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -23,11 +23,12 @@
<bool name="profile_supported_pan">true</bool>
<bool name="profile_supported_pbap">true</bool>
<bool name="profile_supported_gatt">true</bool>
- <bool name="pbap_include_photos_in_vcard">false</bool>
+ <bool name="pbap_include_photos_in_vcard">true</bool>
<bool name="pbap_use_profile_for_owner_vcard">true</bool>
<bool name="profile_supported_map">true</bool>
<bool name="profile_supported_avrcp_controller">false</bool>
<bool name="profile_supported_sap">false</bool>
+ <bool name="profile_supported_pbapclient">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
@@ -43,4 +44,12 @@
<integer name="gatt_low_power_max_interval">100</integer>
<bool name="headset_client_initial_audio_route_allowed">true</bool>
+
+ <!-- For AVRCP absolute volume feature. If the threshold is non-zero,
+ restrict the initial volume to the threshold.
+ Valid value is 1-14, and recommended value is 8 -->
+ <integer name="a2dp_absolute_volume_initial_threshold">8</integer>
+
+ <!-- For A2DP sink ducking volume feature. -->
+ <integer name="a2dp_sink_duck_percent">25</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c47aaf2..c03034b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -79,10 +79,8 @@
<string name="incoming_file_confirm_timeout_content">There was a timeout while accepting an incoming file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
<!-- Bluetooth File Transfer Acceptance Notification item -->
- <string name="incoming_file_confirm_Notification_title">Bluetooth share: Incoming file</string>
- <string name="incoming_file_confirm_Notification_caption">Do you want to receive this file?</string>
- <string name="incoming_file_toast_msg">Incoming file from another device.
- Confirm you want to receive this file.</string>
+ <string name="incoming_file_confirm_Notification_title">Incoming file</string>
+ <string name="incoming_file_confirm_Notification_content"><xliff:g id="sender">%1$s</xliff:g> is ready to send <xliff:g id="file">%2$s</xliff:g></string>
<!-- Inbound File Transfer Progress Notification item -->
<!-- label for the notification item of receiving file -->
@@ -240,9 +238,11 @@
<string name="bluetooth_map_settings_save">Save</string>
<string name="bluetooth_map_settings_cancel">Cancel</string>
- <string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any acceess to the accounts when connecting.</string>
+ <string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting.</string>
<string name="bluetooth_map_settings_count">Slots left:</string>
<string name="bluetooth_map_settings_app_icon">Application Icon</string>
<string name="bluetooth_map_settings_title">Bluetooth Message Sharing Settings</string>
<string name="bluetooth_map_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
+ <string name="bluetooth_connected">Bluetooth audio connected</string>
+ <string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
</resources>
diff --git a/res/values/strings_pbap_client.xml b/res/values/strings_pbap_client.xml
new file mode 100644
index 0000000..465d4ee
--- /dev/null
+++ b/res/values/strings_pbap_client.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_account_type">com.android.bluetooth.pbapsink</string>
+</resources>
diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml
new file mode 100644
index 0000000..f93edea
--- /dev/null
+++ b/res/xml/authenticator.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<account-authenticator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="@string/pbap_account_type" />
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 6a467f9..66d2172 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -211,8 +211,9 @@
ok = (foregroundUser == callingUser);
if (!ok) {
// Always allow SystemUI/System access.
- int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
- "com.android.systemui", UserHandle.USER_OWNER);
+ final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
+ "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
+ UserHandle.USER_SYSTEM);
ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
}
} catch (Exception ex) {
@@ -245,8 +246,9 @@
(foregroundUser == parentUser);
if (!ok) {
// Always allow SystemUI/System access.
- int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
- "com.android.systemui", UserHandle.USER_OWNER);
+ final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
+ "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
+ UserHandle.USER_SYSTEM);
ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
}
} catch (Exception ex) {
@@ -289,8 +291,13 @@
}
// Enforce location permission for apps targeting M and later versions
if (isMApp(context, callingPackage)) {
- throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
- + "ACCESS_FINE_LOCATION permission to get scan results");
+ // PEERS_MAC_ADDRESS is another way to get scan results without
+ // requiring location permissions, so only throw an exception here
+ // if PEERS_MAC_ADDRESS permission is missing as well
+ if (!checkCallerHasPeersMacAddressPermission(context)) {
+ throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
+ + "ACCESS_FINE_LOCATION permission to get scan results");
+ }
} else {
// Pre-M apps running in the foreground should continue getting scan results
if (isForegroundApp(context, callingPackage)) {
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index e14302c..41c31d1 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -213,6 +213,12 @@
mAvrcp.setA2dpAudioState(state);
}
+ public void resetAvrcpBlacklist(BluetoothDevice device) {
+ if (mAvrcp != null) {
+ mAvrcp.resetBlackList(device.getAddress());
+ }
+ }
+
synchronized boolean isA2dpPlaying(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM,
"Need BLUETOOTH permission");
diff --git a/src/com/android/bluetooth/a2dp/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
similarity index 62%
rename from src/com/android/bluetooth/a2dp/A2dpSinkService.java
rename to src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 5dcec73..82d6d34 100644
--- a/src/com/android/bluetooth/a2dp/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -14,13 +14,17 @@
* limitations under the License.
*/
-package com.android.bluetooth.a2dp;
+package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothAudioConfig;
+import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothA2dpSink;
+import android.content.Intent;
+import android.provider.Settings;
import android.util.Log;
+import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
@@ -32,7 +36,7 @@
* @hide
*/
public class A2dpSinkService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "A2dpSinkService";
private A2dpSinkStateMachine mStateMachine;
@@ -47,13 +51,24 @@
}
protected boolean start() {
+ if (DBG) {
+ Log.d(TAG, "start()");
+ }
+ // Start the media browser service.
+ Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
+ startService(startIntent);
mStateMachine = A2dpSinkStateMachine.make(this, this);
setA2dpSinkService(this);
return true;
}
protected boolean stop() {
+ if (DBG) {
+ Log.d(TAG, "stop()");
+ }
mStateMachine.doQuit();
+ Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
+ stopService(stopIntent);
return true;
}
@@ -143,6 +158,74 @@
return mStateMachine.getConnectionState(device);
}
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+ priority);
+ if (DBG) {
+ Log.d(TAG,"Saved priority " + device + " = " + priority);
+ }
+ return true;
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ int priority = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+ BluetoothProfile.PRIORITY_UNDEFINED);
+ return priority;
+ }
+
+ /**
+ * Called by AVRCP controller to provide information about the last user intent on CT.
+ *
+ * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
+ * any incoming sound from the phone (and also retain focus for a few seconds before
+ * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
+ * component will take the focus away but also notify the stack to throw away incoming data.
+ */
+ public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+ if (mStateMachine != null) {
+ if (keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY &&
+ keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
+ } else if ((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE ||
+ keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP) &&
+ keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
+ }
+ }
+ }
+
+ /**
+ * Called by AVRCP controller to provide information about the last user intent on TG.
+ *
+ * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
+ * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
+ * stopping playback.
+ */
+ public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
+ if (mStateMachine != null) {
+ if (!isPlaying) {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
+ } else {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
+ }
+ }
+ }
+
+ synchronized boolean isA2dpPlaying(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ if (DBG) {
+ Log.d(TAG, "isA2dpPlaying(" + device + ")");
+ }
+ return mStateMachine.isPlaying(device);
+ }
+
BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mStateMachine.getAudioConfig(device);
@@ -204,6 +287,24 @@
return service.getConnectionState(device);
}
+ public boolean isA2dpPlaying(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return false;
+ return service.isA2dpPlaying(device);
+ }
+
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ A2dpSinkService service = getService();
+ if (service == null) return false;
+ return service.setPriority(device, priority);
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+ return service.getPriority(device);
+ }
+
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
A2dpSinkService service = getService();
if (service == null) return null;
diff --git a/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
similarity index 88%
rename from src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
rename to src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index d57a0ca..07acd6f 100644
--- a/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,7 +15,7 @@
*/
/**
- * Bluetooth A2dp StateMachine
+ * Bluetooth A2dp Sink StateMachine
* (Disconnected)
* | ^
* CONNECT | | DISCONNECTED
@@ -24,50 +24,68 @@
* | ^
* CONNECTED | | CONNECT
* V |
- * (Connected)
+ * (Connected -- See A2dpSinkStreamingStateMachine)
*/
-package com.android.bluetooth.a2dp;
+package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.media.AudioFormat;
-import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
-import android.content.Intent;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ParcelUuid;
import android.util.Log;
+
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.avrcp.AvrcpControllerService;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Set;
final class A2dpSinkStateMachine extends StateMachine {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
static final int CONNECT = 1;
static final int DISCONNECT = 2;
private static final int STACK_EVENT = 101;
private static final int CONNECT_TIMEOUT = 201;
+ public static final int EVENT_AVRCP_CT_PLAY = 301;
+ public static final int EVENT_AVRCP_CT_PAUSE = 302;
+ public static final int EVENT_AVRCP_TG_PLAY = 303;
+ public static final int EVENT_AVRCP_TG_PAUSE = 304;
+ private static final int IS_INVALID_DEVICE = 0;
+ private static final int IS_VALID_DEVICE = 1;
+ public static final int AVRC_ID_PLAY = 0x44;
+ public static final int AVRC_ID_PAUSE = 0x46;
+ public static final int KEY_STATE_PRESSED = 0;
+ public static final int KEY_STATE_RELEASED = 1;
+
+ // Connection states.
+ // 1. Disconnected: The connection does not exist.
+ // 2. Pending: The connection is being established.
+ // 3. Connected: The connection is established. The audio connection is in Idle state.
private Disconnected mDisconnected;
private Pending mPending;
private Connected mConnected;
@@ -75,12 +93,12 @@
private A2dpSinkService mService;
private Context mContext;
private BluetoothAdapter mAdapter;
- private final AudioManager mAudioManager;
private IntentBroadcastHandler mIntentBroadcastHandler;
- private final WakeLock mWakeLock;
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
@@ -106,6 +124,7 @@
private BluetoothDevice mCurrentDevice = null;
private BluetoothDevice mTargetDevice = null;
private BluetoothDevice mIncomingDevice = null;
+ private BluetoothDevice mPlayingDevice = null;
private final HashMap<BluetoothDevice,BluetoothAudioConfig> mAudioConfigs
= new HashMap<BluetoothDevice,BluetoothAudioConfig>();
@@ -133,11 +152,8 @@
setInitialState(mDisconnected);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpSinkService");
mIntentBroadcastHandler = new IntentBroadcastHandler();
-
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
@@ -320,6 +336,7 @@
break;
case STACK_EVENT:
StackEvent event = (StackEvent) message.obj;
+ log("STACK_EVENT " + event.type);
switch (event.type) {
case EVENT_TYPE_CONNECTION_STATE_CHANGED:
removeMessages(CONNECT_TIMEOUT);
@@ -341,6 +358,7 @@
// in Pending state
private void processConnectionEvent(int state, BluetoothDevice device) {
+ log("processConnectionEvent state " + state);
switch (state) {
case CONNECTION_STATE_DISCONNECTED:
mAudioConfigs.remove(device);
@@ -370,7 +388,7 @@
}
} else if (mTargetDevice != null && mTargetDevice.equals(device)) {
// outgoing connection failed
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
synchronized (A2dpSinkStateMachine.this) {
mTargetDevice = null;
@@ -390,6 +408,7 @@
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);
@@ -402,6 +421,7 @@
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) {
@@ -410,6 +430,7 @@
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) {
@@ -471,12 +492,14 @@
}
private class Connected extends State {
+ private A2dpSinkStreamingStateMachine mStreaming;
@Override
public void enter() {
log("Enter Connected: " + getCurrentMessage().what);
// Upon connected, the audio starts out as stopped
broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
BluetoothA2dpSink.STATE_PLAYING);
+ mStreaming = A2dpSinkStreamingStateMachine.make(A2dpSinkStateMachine.this, mContext);
}
@Override
@@ -487,7 +510,6 @@
return NOT_HANDLED;
}
- boolean retValue = HANDLED;
switch(message.what) {
case CONNECT:
{
@@ -506,10 +528,12 @@
synchronized (A2dpSinkStateMachine.this) {
mTargetDevice = device;
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
transitionTo(mPending);
}
}
- break;
+ break;
+
case DISCONNECT:
{
BluetoothDevice device = (BluetoothDevice) message.obj;
@@ -523,9 +547,12 @@
BluetoothProfile.STATE_DISCONNECTED);
break;
}
+ mPlayingDevice = null;
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
transitionTo(mPending);
}
- break;
+ break;
+
case STACK_EVENT:
StackEvent event = (StackEvent) message.obj;
switch (event.type) {
@@ -543,10 +570,22 @@
break;
}
break;
+
+ case EVENT_AVRCP_CT_PLAY:
+ case EVENT_AVRCP_TG_PLAY:
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.ACT_PLAY);
+ break;
+
+ case EVENT_AVRCP_CT_PAUSE:
+ case EVENT_AVRCP_TG_PAUSE:
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.ACT_PAUSE);
+ break;
+
+
default:
return NOT_HANDLED;
}
- return retValue;
+ return HANDLED;
}
// in Connected state
@@ -554,10 +593,15 @@
switch (state) {
case CONNECTION_STATE_DISCONNECTED:
mAudioConfigs.remove(device);
+ if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
+ mPlayingDevice = null;
+ }
if (mCurrentDevice.equals(device)) {
broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED);
synchronized (A2dpSinkStateMachine.this) {
+ // Take care of existing audio focus in the streaming state machine.
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
mCurrentDevice = null;
transitionTo(mDisconnected);
}
@@ -570,21 +614,21 @@
break;
}
}
+
private void processAudioStateEvent(int state, BluetoothDevice device) {
if (!mCurrentDevice.equals(device)) {
loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
mCurrentDevice);
return;
}
+ log(" processAudioStateEvent in state " + state);
switch (state) {
case AUDIO_STATE_STARTED:
- broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
- BluetoothA2dpSink.STATE_NOT_PLAYING);
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.SRC_STR_START);
break;
case AUDIO_STATE_REMOTE_SUSPEND:
case AUDIO_STATE_STOPPED:
- broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
+ mStreaming.sendMessage(A2dpSinkStreamingStateMachine.SRC_STR_STOP);
break;
default:
loge("Audio State Device: " + device + " bad state: " + state);
@@ -594,6 +638,7 @@
}
private void processAudioConfigEvent(BluetoothAudioConfig audioConfig, BluetoothDevice device) {
+ log("processAudioConfigEvent: " + device);
mAudioConfigs.put(device, audioConfig);
broadcastAudioConfig(device, audioConfig);
}
@@ -644,6 +689,15 @@
return devices;
}
+ boolean isPlaying(BluetoothDevice device) {
+ synchronized(this) {
+ if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean okToConnect(BluetoothDevice device) {
AdapterService adapterService = AdapterService.getAdapterService();
boolean ret = true;
@@ -680,10 +734,7 @@
// This method does not check for error conditon (newState == prevState)
private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
- int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
- BluetoothProfile.A2DP_SINK);
-
- mWakeLock.acquire();
+ int delay = 0;
mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
MSG_CONNECTION_STATE_CHANGED,
prevState,
@@ -775,12 +826,29 @@
switch (msg.what) {
case MSG_CONNECTION_STATE_CHANGED:
onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- mWakeLock.release();
break;
}
}
}
+ public boolean SendPassThruPlay(BluetoothDevice mDevice) {
+ log("SendPassThruPlay + ");
+ AvrcpControllerService avrcpCtrlService = AvrcpControllerService.getAvrcpControllerService();
+ if ((avrcpCtrlService != null) && (mDevice != null) &&
+ (avrcpCtrlService.getConnectedDevices().contains(mDevice))){
+ avrcpCtrlService.sendPassThroughCmd(mDevice,
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+ BluetoothAvrcpController.KEY_STATE_PRESSED);
+ avrcpCtrlService.sendPassThroughCmd(mDevice,
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+ BluetoothAvrcpController.KEY_STATE_RELEASED);
+ log(" SendPassThruPlay command sent - ");
+ return true;
+ } else {
+ log("passthru command not sent, connection unavailable");
+ return false;
+ }
+ }
// Event types for STACK_EVENT message
final private static int EVENT_TYPE_NONE = 0;
@@ -788,7 +856,7 @@
final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
final private static int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
- // Do not modify without updating the HAL bt_av.h files.
+ // Do not modify without updating the HAL bt_av.h files.
// match up with btav_connection_state_t enum of bt_av.h
final static int CONNECTION_STATE_DISCONNECTED = 0;
@@ -806,4 +874,6 @@
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/A2dpSinkStreamingStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
new file mode 100644
index 0000000..fb62c95
--- /dev/null
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dpsink;
+
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.R;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+/**
+ * Bluetooth A2DP SINK Streaming StateMachine.
+ *
+ * This state machine defines how the stack behaves once the A2DP connection is established and both
+ * devices are ready for streaming. For simplification we assume that the connection can either
+ * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
+ * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
+ *
+ * Legend:
+ * SRC: Source (Remote)
+ * (SRC, F) - Remote is not streaming. F stands for false.
+ * (SRC, T) - Remote is intent to streaming. T stands for true.
+ * ACT: Action
+ * (ACT, F) - Local/Remote user intent is to pause/stop (AVRCP pause/stop).
+ * (ACT, T) - Local/Remote user intent is to play (AVRCP play).
+ * The way to detect action is two fold:
+ * -- We can detect action on the SNK side by directly monitoring the AVRCP controller service.
+ * -- On the SRC side, any AVRCP action will be accompanied by an update via AVRCP and hence we can
+ * update our action state.
+ *
+ * A state will be a combination of SRC and ACT state. Hence a state such as:
+ * (F, T) will mean that user has shown intent to play on local or remote device (second T) but the
+ * connection is not in streaming state yet.
+ *
+ * -----------------------------------------------------------------------------------------------
+ * Start State | End State | Transition(s)
+ * -----------------------------------------------------------------------------------------------
+ * (F, F) (F, T) ACT Play (No streaming in either states)
+ * (F, F) (T, F) Remote streams (play depends on policy)
+ * (T, F) (F, F) Remote stops streaming.
+ * (T, F) (T, T) ACT Play (streaming already existed).
+ * (F, T) (F, F) ACT Pause.
+ * (F, T) (T, T) Remote starts streaming (ACT Play already existed)
+ * (T, T) (F, T) Remote stops streaming.
+ * (T, T) (F, F) ACT stop.
+ * (T, T) (T, F) ACT pause.
+ *
+ * -----------------------------------------------------------------------------------------------
+ * State | Action(s)
+ * -----------------------------------------------------------------------------------------------
+ * (F, F) 1. Lose audio focus (if it exists) and notify fluoride of audio focus loss.
+ * 2. Stop AVRCP from pushing updates to UI.
+ * (T, F) 1. If policy is opt-in then get focus and stream (get audio focus etc).
+ * 2. Else throw away the data (lose audio focus etc).
+ * (F, T) In this state the source does not stream although we have play intent.
+ * 1. Show a spinny that data will come through.
+ * (T, T) 1. Request Audio Focus and on success update AVRCP to show UI updates.
+ * 2. On Audio focus enable streaming in Fluoride.
+ */
+final class A2dpSinkStreamingStateMachine extends StateMachine {
+ private static final boolean DBG = true;
+ private static final String TAG = "A2dpSinkStreamingStateMachine";
+ private static final int ACT_PLAY_NUM_RETRIES = 5;
+ private static final int ACT_PLAY_RETRY_DELAY = 2000; // millis.
+ private static final int DEFAULT_DUCK_PERCENT = 25;
+
+ // Streaming states (see the description above).
+ private SRC_F_ACT_F mSrcFActF;
+ private SRC_F_ACT_T mSrcFActT;
+ private SRC_T_ACT_F mSrcTActF;
+ private SRC_T_ACT_T mSrcTActT;
+
+ // Transitions.
+ public static final int SRC_STR_START = 0;
+ public static final int SRC_STR_STOP = 1;
+ public static final int SRC_STR_STOP_JITTER_WAIT_OVER = 2;
+ public static final int ACT_PLAY = 3;
+ public static final int ACT_PLAY_RETRY = 4;
+ public static final int ACT_PAUSE = 5;
+ public static final int AUDIO_FOCUS_CHANGE = 6;
+ public static final int DISCONNECT = 7;
+
+ // Private variables.
+ private A2dpSinkStateMachine mA2dpSinkSm;
+ private Context mContext;
+ private AudioManager mAudioManager;
+ // Set default focus to loss since we have never requested it before.
+ private int mCurrentAudioFocus = AudioManager.AUDIOFOCUS_LOSS;
+
+ /* Used to indicate focus lost */
+ private static final int STATE_FOCUS_LOST = 0;
+ /* Used to inform bluedroid that focus is granted */
+ private static final int STATE_FOCUS_GRANTED = 1;
+
+ /* Wait in millis before the ACT loses focus on SRC jitter when streaming */
+ private static final int SRC_STR_JITTER_WAIT = 5 * 1000; // 5sec
+
+ /* Focus changes when we are currently holding focus (i.e. we're in SRC_T_ACT_T state). */
+ private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
+ public void onAudioFocusChange(int focusChange){
+ if (DBG) {
+ Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
+ }
+ A2dpSinkStreamingStateMachine.this.sendMessage(AUDIO_FOCUS_CHANGE, focusChange);
+ }
+ };
+
+ private A2dpSinkStreamingStateMachine(A2dpSinkStateMachine a2dpSinkSm, Context context) {
+ super("A2dpSinkStreamingStateMachine");
+ mA2dpSinkSm = a2dpSinkSm;
+ mContext = context;
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ mSrcFActF = new SRC_F_ACT_F();
+ mSrcFActT = new SRC_F_ACT_T();
+ mSrcTActF = new SRC_T_ACT_F();
+ mSrcTActT = new SRC_T_ACT_T();
+
+ // States are independent of each other. We simply use transitionTo.
+ addState(mSrcFActF);
+ addState(mSrcFActT);
+ addState(mSrcTActF);
+ addState(mSrcTActT);
+ setInitialState(mSrcFActF);
+
+ }
+
+ public static A2dpSinkStreamingStateMachine make(
+ A2dpSinkStateMachine a2dpSinkSm, Context context) {
+ if (DBG) {
+ Log.d(TAG, "make");
+ }
+ A2dpSinkStreamingStateMachine a2dpStrStateMachine =
+ new A2dpSinkStreamingStateMachine(a2dpSinkSm, context);
+ a2dpStrStateMachine.start();
+ return a2dpStrStateMachine;
+ }
+
+ /**
+ * Utility functions that can be used by all states.
+ */
+ private boolean requestAudioFocus() {
+ return (mAudioManager.requestAudioFocus(
+ mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) ==
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ }
+
+ private void startAvrcpUpdates() {
+ // Since AVRCP gets started after A2DP we may need to request it later in cycle.
+ AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+
+ if (DBG) {
+ Log.d(TAG, "startAvrcpUpdates");
+ }
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
+ avrcpService.startAvrcpUpdates();
+ } else {
+ Log.e(TAG, "startAvrcpUpdates failed because of connection.");
+ }
+ }
+
+ private void stopAvrcpUpdates() {
+ // Since AVRCP gets started after A2DP we may need to request it later in cycle.
+ AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+
+ if (DBG) {
+ Log.d(TAG, "stopAvrcpUpdates");
+ }
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
+ avrcpService.stopAvrcpUpdates();
+ } else {
+ Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
+ }
+ }
+
+ private void sendAvrcpPause() {
+ // Since AVRCP gets started after A2DP we may need to request it later in cycle.
+ AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+
+ if (DBG) {
+ Log.d(TAG, "sendAvrcpPause");
+ }
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
+ if (DBG) {
+ Log.d(TAG, "Pausing AVRCP.");
+ }
+ avrcpService.sendPassThroughCmd(
+ avrcpService.getConnectedDevices().get(0),
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
+ BluetoothAvrcpController.KEY_STATE_PRESSED);
+ avrcpService.sendPassThroughCmd(
+ avrcpService.getConnectedDevices().get(0),
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
+ BluetoothAvrcpController.KEY_STATE_RELEASED);
+ } else {
+ Log.e(TAG, "Passthrough not sent, connection un-available.");
+ }
+ }
+
+ private void sendAvrcpPlay() {
+ // Since AVRCP gets started after A2DP we may need to request it later in cycle.
+ AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+
+ if (DBG) {
+ Log.d(TAG, "sendAvrcpPlay");
+ }
+ if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
+ if (DBG) {
+ Log.d(TAG, "Playing AVRCP.");
+ }
+ avrcpService.sendPassThroughCmd(
+ avrcpService.getConnectedDevices().get(0),
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+ BluetoothAvrcpController.KEY_STATE_PRESSED);
+ avrcpService.sendPassThroughCmd(
+ avrcpService.getConnectedDevices().get(0),
+ BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
+ BluetoothAvrcpController.KEY_STATE_RELEASED);
+ } else {
+ Log.e(TAG, "Passthrough not sent, connection un-available.");
+ }
+ }
+
+ private void startFluorideStreaming() {
+ mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+ mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+ }
+
+ private void stopFluorideStreaming() {
+ mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+ }
+
+ private void setFluorideAudioTrackGain(float gain) {
+ mA2dpSinkSm.informAudioTrackGainNative(gain);
+ }
+
+ private class SRC_F_ACT_F extends State {
+ private static final String STATE_TAG = A2dpSinkStreamingStateMachine.TAG + ".SRC_F_ACT_F";
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.d(STATE_TAG, "Enter: " + getCurrentMessage().what);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ Log.d(STATE_TAG, " process message: " + message.what);
+ }
+ switch (message.what) {
+ case SRC_STR_START:
+ // Opt out of all sounds without AVRCP play. We simply throw away.
+ transitionTo(mSrcTActF);
+ break;
+
+ case ACT_PLAY:
+ // Wait in next state for actual playback. We defer the message so that the next
+ // state (SRC_F_ACT_T) can execute the retry logic.
+ deferMessage(message);
+ transitionTo(mSrcFActT);
+ break;
+
+ case DISCONNECT:
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mCurrentAudioFocus = AudioManager.AUDIOFOCUS_LOSS;
+ break;
+
+ case AUDIO_FOCUS_CHANGE:
+ // If we are regaining focus after transient loss this indicates that we should
+ // press play again.
+ int newAudioFocus = message.arg1;
+ if (DBG) {
+ Log.d(STATE_TAG,
+ "prev focus " + mCurrentAudioFocus + " new focus " + newAudioFocus);
+ }
+ if (mCurrentAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT &&
+ newAudioFocus == AudioManager.AUDIOFOCUS_GAIN) {
+ sendAvrcpPlay();
+ // We should transition to SRC_F_ACT_T after this message. We also send some
+ // retries here because after phone calls we may have race conditions.
+ sendMessageDelayed(
+ ACT_PLAY_RETRY, ACT_PLAY_NUM_RETRIES, ACT_PLAY_RETRY_DELAY);
+ }
+ mCurrentAudioFocus = newAudioFocus;
+ break;
+
+ default:
+ Log.e(TAG, "Don't know how to handle " + message.what);
+ }
+ return HANDLED;
+ }
+ }
+
+ private class SRC_F_ACT_T extends State {
+ private static final String STATE_TAG = A2dpSinkStreamingStateMachine.TAG + ".SRC_F_ACT_T";
+ private boolean mPlay = false;
+
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.d(STATE_TAG, "Enter: " + getCurrentMessage().what);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ Log.d(STATE_TAG, " process message: " + message.what);
+ }
+ switch (message.what) {
+ case SRC_STR_START:
+ deferMessage(message);
+ transitionTo(mSrcTActT);
+ break;
+
+ case ACT_PAUSE:
+ transitionTo(mSrcFActF);
+ break;
+
+ case ACT_PLAY:
+ // Retry if the remote has not yet started playing music. This is seen in some
+ // devices where after the phone call it requires multiple play commands to
+ // start music.
+ break;
+
+ case ACT_PLAY_RETRY:
+ if (message.arg1 > 0) {
+ Log.d(STATE_TAG, "Retry " + message.arg1);
+ sendAvrcpPlay();
+ sendMessageDelayed(ACT_PLAY_RETRY, message.arg1 - 1, ACT_PLAY_RETRY_DELAY);
+ }
+ break;
+
+
+ case DISCONNECT:
+ deferMessage(message);
+ transitionTo(mSrcFActF);
+ mPlay = false;
+ break;
+
+ case AUDIO_FOCUS_CHANGE:
+ int newAudioFocus = message.arg1;
+ if (DBG) {
+ Log.d(STATE_TAG,
+ "prev focus " + mCurrentAudioFocus + " new focus " + newAudioFocus);
+ }
+ if (newAudioFocus == AudioManager.AUDIOFOCUS_GAIN) {
+ sendAvrcpPlay();
+ mCurrentAudioFocus = newAudioFocus;
+ } else if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ sendAvrcpPause();
+ mCurrentAudioFocus = newAudioFocus;
+ } else if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS) {
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mCurrentAudioFocus = AudioManager.AUDIOFOCUS_LOSS;
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Don't know how to handle " + message.what);
+ }
+ return HANDLED;
+ }
+ }
+
+ private class SRC_T_ACT_F extends State {
+ private static final String STATE_TAG = A2dpSinkStreamingStateMachine.TAG + ".SRC_T_ACT_F";
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.d(STATE_TAG, "Enter: " + getCurrentMessage().what);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ Log.d(STATE_TAG, " process message: " + message.what);
+ }
+ switch (message.what) {
+ case SRC_STR_STOP:
+ transitionTo(mSrcFActF);
+ break;
+
+ case ACT_PLAY:
+ deferMessage(message);
+ transitionTo(mSrcTActT);
+ break;
+
+ case DISCONNECT:
+ deferMessage(message);
+ transitionTo(mSrcFActF);
+ break;
+
+ case AUDIO_FOCUS_CHANGE:
+ // If we regain focus from TRANSIENT that means that the remote was playing all
+ // this while although we must have sent a PAUSE (see focus loss in SRC_T_ACT_T
+ // state). In any case, we should resume music here if that is the case.
+ int newAudioFocus = message.arg1;
+ if (DBG) {
+ Log.d(STATE_TAG,
+ "prev focus " + mCurrentAudioFocus + " new focus " + newAudioFocus);
+ }
+ if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS ||
+ newAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ sendAvrcpPause();
+ } else if (newAudioFocus == AudioManager.AUDIOFOCUS_GAIN) {
+ sendAvrcpPlay();
+ }
+ mCurrentAudioFocus = newAudioFocus;
+ break;
+
+ default:
+ Log.e(TAG, "Don't know how to handle " + message.what);
+ }
+ return HANDLED;
+ }
+ }
+
+ private class SRC_T_ACT_T extends State {
+ private static final String STATE_TAG = A2dpSinkStreamingStateMachine.TAG + ".SRC_T_ACT_T";
+ private boolean mWaitForJitter = false;
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.d(STATE_TAG, "Enter: " + getCurrentMessage().what);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ Log.d(STATE_TAG, " process message: " + message.what);
+ }
+ switch (message.what) {
+ case ACT_PAUSE:
+ // Stop avrcp updates.
+ stopAvrcpUpdates();
+ stopFluorideStreaming();
+ transitionTo(mSrcTActF);
+ if (mCurrentAudioFocus == AudioManager.AUDIOFOCUS_GAIN) {
+ // If we have focus gain and we still get pause that means that we must have
+ // gotten a PAUSE by user explicitly pressing PAUSE on Car or Phone. Hence
+ // we release focus.
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mCurrentAudioFocus = AudioManager.AUDIOFOCUS_LOSS;
+ }
+ break;
+
+ case SRC_STR_STOP:
+ stopAvrcpUpdates();
+ stopFluorideStreaming();
+ transitionTo(mSrcFActT);
+ // This could be variety of reasons including that the remote is going to
+ // (eventually send us pause) or the device is going to go into a call state
+ // etc. Also it may simply be stutter of music. Instead of sending pause
+ // prematurely we wait for either a Pause from remote or AudioFocus change owing
+ // an ongoing call.
+ break;
+
+ case SRC_STR_START:
+ case ACT_PLAY:
+ Log.d(STATE_TAG, "Current Audio Focus " + mCurrentAudioFocus);
+ boolean startStream = true;
+ if (mCurrentAudioFocus == AudioManager.AUDIOFOCUS_LOSS) {
+ if (!requestAudioFocus()) {
+ Log.e(STATE_TAG, "Cannot get focus, hence not starting streaming.");
+ startStream = false;
+ } else {
+ mCurrentAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
+ }
+ }
+ if (startStream) {
+ startAvrcpUpdates();
+ startFluorideStreaming();
+ }
+ // If we did not get focus, it may mean that the device in a call state and
+ // hence we should wait for an audio focus event.
+ break;
+
+ // On Audio Focus events we stay in the same state but this can potentially change
+ // if we playback.
+ case AUDIO_FOCUS_CHANGE:
+ int newAudioFocus = (int) message.arg1;
+ if (DBG) {
+ Log.d(STATE_TAG,
+ "prev focus " + mCurrentAudioFocus + " new focus " + newAudioFocus);
+ }
+
+ if (newAudioFocus == AudioManager.AUDIOFOCUS_GAIN) {
+ // We have gained focus so play with 1.0 gain.
+ sendAvrcpPlay();
+ startAvrcpUpdates();
+ startFluorideStreaming();
+ setFluorideAudioTrackGain(1.0f);
+ } else if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ // Make the volume duck.
+ int duckPercent =
+ mContext.getResources().getInteger(R.integer.a2dp_sink_duck_percent);
+ if (duckPercent < 0 || duckPercent > 100) {
+ Log.e(STATE_TAG, "Invalid duck percent using default.");
+ duckPercent = DEFAULT_DUCK_PERCENT;
+ }
+ float duckRatio = (float) ((duckPercent * 1.0f) / 100);
+ Log.d(STATE_TAG,
+ "Setting reduce gain on transient loss gain=" + duckRatio);
+ setFluorideAudioTrackGain(duckRatio);
+ } else {
+ // We either are in transient loss or we are in permanent loss,
+ // either ways we should stop streaming.
+ sendAvrcpPause();
+ stopAvrcpUpdates();
+ stopFluorideStreaming();
+
+ // If it is permanent focus loss then we should abandon focus here and wait
+ // for user to explicitly play again.
+ if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS) {
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mCurrentAudioFocus = AudioManager.AUDIOFOCUS_LOSS;
+ }
+ }
+ mCurrentAudioFocus = newAudioFocus;
+ break;
+
+ case DISCONNECT:
+ deferMessage(message);
+ transitionTo(mSrcFActF);
+ break;
+
+ default:
+ Log.e(TAG, "Don't know how to handle " + message.what);
+ }
+ return HANDLED;
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
new file mode 100644
index 0000000..7ee0276
--- /dev/null
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dpsink.mbs;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.util.Pair;
+import android.util.Log;
+
+import com.android.bluetooth.R;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class A2dpMediaBrowserService extends MediaBrowserService {
+ private static final String TAG = "A2dpMediaBrowserService";
+ private static final String MEDIA_ID_ROOT = "__ROOT__";
+ private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
+ private static final float PLAYBACK_SPEED = 1.0f;
+
+ // Message sent when A2DP device is disconnected.
+ private static final int MSG_DEVICE_DISCONNECT = 0;
+ // Message snet when the AVRCP profile is disconnected = 1;
+ private static final int MSG_PROFILE_DISCONNECT = 1;
+ // Message sent when A2DP device is connected.
+ private static final int MSG_DEVICE_CONNECT = 2;
+ // Message sent when AVRCP profile is connected (note AVRCP profile may be connected before or
+ // after A2DP device is connected).
+ private static final int MSG_PROFILE_CONNECT = 3;
+ // Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
+ private static final int MSG_TRACK = 4;
+ // Internal message sent to trigger a AVRCP action.
+ private static final int MSG_AVRCP_PASSTHRU = 5;
+
+ private MediaSession mSession;
+ private MediaMetadata mA2dpMetadata;
+
+ private BluetoothAdapter mAdapter;
+ private BluetoothAvrcpController mAvrcpProfile;
+ private BluetoothDevice mA2dpDevice = null;
+ private Handler mAvrcpCommandQueue;
+
+ private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
+ | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+
+ private static final class AvrcpCommandQueueHandler extends Handler {
+ WeakReference<A2dpMediaBrowserService> mInst;
+
+ AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
+ super(looper);
+ mInst = new WeakReference<A2dpMediaBrowserService>(sink);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ A2dpMediaBrowserService inst = mInst.get();
+ if (inst == null) {
+ Log.e(TAG, "Parent class has died; aborting.");
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_DEVICE_CONNECT:
+ inst.msgDeviceConnect((BluetoothDevice) msg.obj);
+ break;
+ case MSG_PROFILE_CONNECT:
+ inst.msgProfileConnect((BluetoothProfile) msg.obj);
+ break;
+ case MSG_DEVICE_DISCONNECT:
+ inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
+ break;
+ case MSG_PROFILE_DISCONNECT:
+ inst.msgProfileDisconnect();
+ break;
+ case MSG_TRACK:
+ Pair<PlaybackState, MediaMetadata> pair =
+ (Pair<PlaybackState, MediaMetadata>) (msg.obj);
+ inst.msgTrack(pair.first, pair.second);
+ break;
+ case MSG_AVRCP_PASSTHRU:
+ inst.msgPassThru((int) msg.obj);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate");
+ super.onCreate();
+ mSession = new MediaSession(this, TAG);
+ setSessionToken(mSession.getSessionToken());
+ mSession.setCallback(mSessionCallbacks);
+ mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
+ MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.AVRCP_CONTROLLER);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+ registerReceiver(mBtReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ mSession.release();
+ unregisterReceiver(mBtReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ return new BrowserRoot(MEDIA_ID_ROOT, null);
+ }
+
+ @Override
+ public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+ Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
+ List<MediaItem> items = new ArrayList<MediaItem>();
+ result.sendResult(items);
+ }
+
+ BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ Log.d(TAG, "onServiceConnected");
+ if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
+ mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_CONNECT, proxy).sendToTarget();
+ List<BluetoothDevice> devices = proxy.getConnectedDevices();
+ if (devices != null && devices.size() > 0) {
+ BluetoothDevice device = devices.get(0);
+ Log.d(TAG, "got AVRCP device " + device);
+ }
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ Log.d(TAG, "onServiceDisconnected " + profile);
+ if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
+ mAvrcpProfile = null;
+ mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_DISCONNECT).sendToTarget();
+ }
+ }
+ };
+
+ // Media Session Stuff.
+ private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
+ @Override
+ public void onPlay() {
+ Log.d(TAG, "onPlay");
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY).sendToTarget();
+ // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+ }
+
+ @Override
+ public void onPause() {
+ Log.d(TAG, "onPause");
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
+ // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+ }
+
+ @Override
+ public void onSkipToNext() {
+ Log.d(TAG, "onSkipToNext");
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD)
+ .sendToTarget();
+ // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ Log.d(TAG, "onSkipToPrevious");
+
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD)
+ .sendToTarget();
+ // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+ }
+
+ // These are not yet supported.
+ @Override
+ public void onStop() {
+ Log.d(TAG, "onStop");
+ }
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ Log.d(TAG, "onCustomAction action=" + action + " extras=" + extras);
+ }
+
+ @Override
+ public void onPlayFromSearch(String query, Bundle extras) {
+ Log.d(TAG, "playFromSearch not supported in AVRCP");
+ }
+
+ @Override
+ public void onCommand(String command, Bundle args, ResultReceiver cb) {
+ Log.d(TAG, "onCommand command=" + command + " args=" + args);
+ }
+
+ @Override
+ public void onSkipToQueueItem(long queueId) {
+ Log.d(TAG, "onSkipToQueueItem");
+ }
+
+ @Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ Log.d(TAG, "onPlayFromMediaId mediaId=" + mediaId + " extras=" + extras);
+ }
+
+ };
+
+ private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive intent=" + intent);
+ String action = intent.getAction();
+ BluetoothDevice btDev =
+ (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+ Log.d(TAG, "handleConnectionStateChange: newState="
+ + state + " btDev=" + btDev);
+
+ // Connected state will be handled when AVRCP BluetoothProfile gets connected.
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
+ } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ // Set the playback state to unconnected.
+ mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
+ }
+ } else if (BluetoothAvrcpController.ACTION_TRACK_EVENT.equals(action)) {
+ PlaybackState pbb =
+ intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_PLAYBACK);
+ MediaMetadata mmd =
+ intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_METADATA);
+ mAvrcpCommandQueue.obtainMessage(
+ MSG_TRACK, new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
+ }
+ }
+ };
+
+ private void msgDeviceConnect(BluetoothDevice device) {
+ Log.d(TAG, "msgDeviceConnect");
+ // We are connected to a new device via A2DP now.
+ mA2dpDevice = device;
+ refreshInitialPlayingState();
+ }
+
+ private void msgProfileConnect(BluetoothProfile profile) {
+ Log.d(TAG, "msgProfileConnect");
+ if (profile != null) {
+ mAvrcpProfile = (BluetoothAvrcpController) profile;
+ }
+ refreshInitialPlayingState();
+ }
+
+ // Refresh the UI if we have a connected device and AVRCP is initialized.
+ private void refreshInitialPlayingState() {
+ if (mAvrcpProfile == null || mA2dpDevice == null) {
+ Log.d(TAG, "AVRCP Profile " + mAvrcpProfile + " device " + mA2dpDevice);
+ return;
+ }
+
+ List<BluetoothDevice> devices = mAvrcpProfile.getConnectedDevices();
+ if (devices.size() == 0) {
+ Log.w(TAG, "No devices connected yet");
+ return;
+ }
+
+ if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
+ Log.e(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
+ }
+ mA2dpDevice = devices.get(0);
+
+ PlaybackState playbackState = mAvrcpProfile.getPlaybackState(mA2dpDevice);
+ // Add actions required for playback and rebuild the object.
+ PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
+ playbackState = pbb.setActions(mTransportControlFlags).build();
+
+ MediaMetadata mediaMetadata = mAvrcpProfile.getMetadata(mA2dpDevice);
+ Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
+ mSession.setMetadata(mAvrcpProfile.getMetadata(mA2dpDevice));
+ mSession.setPlaybackState(playbackState);
+ }
+
+ private void msgDeviceDisconnect(BluetoothDevice device) {
+ Log.d(TAG, "msgDeviceDisconnect");
+ if (mA2dpDevice == null) {
+ Log.w(TAG, "Already disconnected - nothing to do here.");
+ return;
+ } else if (!mA2dpDevice.equals(device)) {
+ Log.e(TAG, "Not the right device to disconnect current " +
+ mA2dpDevice + " dc " + device);
+ return;
+ }
+
+ // Unset the session.
+ PlaybackState.Builder pbb = new PlaybackState.Builder();
+ pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ PLAYBACK_SPEED)
+ .setActions(mTransportControlFlags)
+ .setErrorMessage(getString(R.string.bluetooth_disconnected));
+ mSession.setPlaybackState(pbb.build());
+ }
+
+ private void msgProfileDisconnect() {
+ Log.d(TAG, "msgProfileDisconnect");
+ // The profile is disconnected - even if the device is still connected we cannot really have
+ // a functioning UI so reset the session.
+ mAvrcpProfile = null;
+
+ // Unset the session.
+ PlaybackState.Builder pbb = new PlaybackState.Builder();
+ pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ PLAYBACK_SPEED)
+ .setActions(mTransportControlFlags)
+ .setErrorMessage(getString(R.string.bluetooth_disconnected));
+ mSession.setPlaybackState(pbb.build());
+ }
+
+ private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
+ Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
+ // Log the current track position/content.
+ MediaController controller = mSession.getController();
+ PlaybackState prevPS = controller.getPlaybackState();
+ MediaMetadata prevMM = controller.getMetadata();
+
+ if (prevPS != null) {
+ Log.d(TAG, "prevPS " + prevPS);
+ }
+
+ if (prevMM != null) {
+ String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
+ long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
+ }
+
+ if (mmd != null) {
+ Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
+ mSession.setMetadata(mmd);
+ }
+
+ if (pb != null) {
+ Log.d(TAG, "msgTrack() playbackstate " + pb);
+ PlaybackState.Builder pbb = new PlaybackState.Builder(pb);
+ pb = pbb.setActions(mTransportControlFlags).build();
+ mSession.setPlaybackState(pb);
+ }
+ }
+
+ private void msgPassThru(int cmd) {
+ Log.d(TAG, "msgPassThru " + cmd);
+ if (mA2dpDevice == null) {
+ // We should have already disconnected - ignore this message.
+ Log.e(TAG, "Already disconnected ignoring.");
+ return;
+ }
+
+ if (mAvrcpProfile == null) {
+ // We may be disconnected with the profile but there is not much we can do for now but
+ // to wait for the profile to come back up.
+ Log.e(TAG, "Profile disconnected; ignoring.");
+ return;
+ }
+
+ // Send the pass through.
+ mAvrcpProfile.sendPassThroughCmd(
+ mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_PRESSED);
+ mAvrcpProfile.sendPassThroughCmd(
+ mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_RELEASED);
+ }
+}
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index abc0b84..945963e 100755
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -19,18 +19,19 @@
import java.util.Timer;
import java.util.TimerTask;
-import android.app.PendingIntent;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAvrcp;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
-import android.media.IRemoteControlDisplay;
-import android.media.MediaMetadataRetriever;
-import android.media.RemoteControlClient;
-import android.media.RemoteController;
-import android.media.RemoteController.MetadataEditor;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -45,6 +46,7 @@
import android.util.Log;
import android.view.KeyEvent;
+import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
@@ -54,6 +56,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
@@ -63,15 +66,18 @@
public final class Avrcp {
private static final boolean DEBUG = false;
private static final String TAG = "Avrcp";
+ private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
private Context mContext;
private final AudioManager mAudioManager;
private AvrcpMessageHandler mHandler;
- private RemoteController mRemoteController;
- private RemoteControllerWeak mRemoteControllerCb;
- private Metadata mMetadata;
+ private MediaSessionManager mMediaSessionManager;
+ private MediaSessionChangeListener mSessionChangeListener;
+ private MediaController mMediaController;
+ private MediaControllerListener mMediaControllerCb;
+ private MediaAttributes mMediaAttributes;
private int mTransportControlFlags;
- private int mCurrentPlayState;
+ private PlaybackState mCurrentPlayState;
private int mPlayStatusChangedNT;
private int mTrackChangedNT;
private long mTrackNumber;
@@ -84,12 +90,23 @@
private long mPrevPosMs;
private long mSkipStartTime;
private int mFeatures;
- private int mAbsoluteVolume;
- private int mLastSetVolume;
+ private int mRemoteVolume;
+ private int mLastRemoteVolume;
+ private int mInitialRemoteVolume;
+
+ /* Local volume in audio index 0-15 */
+ private int mLocalVolume;
+ private int mLastLocalVolume;
+ private int mAbsVolThreshold;
+
+ private String mAddress;
+ private HashMap<Integer, Integer> mVolumeMapping;
+
private int mLastDirection;
private final int mVolumeStep;
private final int mAudioStreamMax;
- private boolean mVolCmdInProgress;
+ private boolean mVolCmdAdjustInProgress;
+ private boolean mVolCmdSetInProgress;
private int mAbsVolRetryTimes;
private int mSkipAmount;
@@ -120,10 +137,6 @@
private static final int MESSAGE_REWIND = 11;
private static final int MESSAGE_CHANGE_PLAY_POS = 12;
private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13;
- private static final int MSG_UPDATE_STATE = 100;
- private static final int MSG_SET_METADATA = 101;
- private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
- private static final int MSG_SET_GENERATION_ID = 104;
private static final int BUTTON_TIMEOUT_TIME = 2000;
private static final int BASE_SKIP_AMOUNT = 2000;
@@ -133,7 +146,7 @@
private static final int SKIP_DOUBLE_INTERVAL = 3000;
private static final long MAX_MULTIPLIER_VALUE = 128L;
private static final int CMD_TIMEOUT_DELAY = 2000;
- private static final int MAX_ERROR_RETRY_TIMES = 3;
+ 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;
@@ -142,30 +155,41 @@
}
private Avrcp(Context context) {
- mMetadata = new Metadata();
- mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
+ mMediaAttributes = new MediaAttributes(null);
+ mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
mTrackNumber = -1L;
- mCurrentPosMs = 0L;
+ mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
mPlayStartTimeMs = -1L;
mSongLengthMs = 0L;
mPlaybackIntervalMs = 0L;
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
mFeatures = 0;
- mAbsoluteVolume = -1;
- mLastSetVolume = -1;
+ mRemoteVolume = -1;
+ mInitialRemoteVolume = -1;
+ mLastRemoteVolume = -1;
mLastDirection = 0;
- mVolCmdInProgress = false;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
mAbsVolRetryTimes = 0;
+ mLocalVolume = -1;
+ mLastLocalVolume = -1;
+ mAbsVolThreshold = 0;
+ mVolumeMapping = new HashMap<Integer, Integer>();
mContext = context;
initNative();
+ mMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
+ Resources resources = context.getResources();
+ if (resources != null) {
+ mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
+ }
}
private void start() {
@@ -173,10 +197,14 @@
thread.start();
Looper looper = thread.getLooper();
mHandler = new AvrcpMessageHandler(looper);
- mRemoteControllerCb = new RemoteControllerWeak(mHandler);
- mRemoteController = new RemoteController(mContext, mRemoteControllerCb);
- mAudioManager.registerRemoteController(mRemoteController);
- mRemoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);
+
+ mSessionChangeListener = new MediaSessionChangeListener();
+ mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null, mHandler);
+ List<MediaController> sessions = mMediaSessionManager.getActiveSessions(null);
+ mMediaControllerCb = new MediaControllerListener();
+ if (sessions.size() > 0) {
+ updateCurrentMediaController(sessions.get(0));
+ }
}
public static Avrcp make(Context context) {
@@ -192,65 +220,61 @@
if (looper != null) {
looper.quit();
}
- mAudioManager.unregisterRemoteController(mRemoteController);
+ mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener);
}
public void cleanup() {
cleanupNative();
+ if (mVolumeMapping != null)
+ mVolumeMapping.clear();
}
- private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener {
- private final WeakReference<Handler> mLocalHandler;
-
- public RemoteControllerWeak(Handler handler) {
- mLocalHandler = new WeakReference<Handler>(handler);
+ private class MediaControllerListener extends MediaController.Callback {
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ Log.v(TAG, "MediaController metadata changed");
+ updateMetadata(metadata);
}
@Override
- public void onClientChange(boolean clearing) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_GENERATION_ID,
- 0, (clearing ? 1 : 0), null).sendToTarget();
- }
+ public void onPlaybackStateChanged(PlaybackState state) {
+ Log.v(TAG, "MediaController playback changed: " + state.toString());
+ updatePlayPauseState(state);
}
@Override
- public void onClientPlaybackStateUpdate(int state) {
- // Should never be called with the existing code, but just in case
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
- new Long(RemoteControlClient.PLAYBACK_POSITION_INVALID)).sendToTarget();
- }
+ public void onSessionDestroyed() {
+ Log.v(TAG, "MediaController session destroyed");
+ }
+ }
+
+ private class MediaSessionChangeListener implements MediaSessionManager.OnActiveSessionsChangedListener {
+ public MediaSessionChangeListener() {
}
@Override
- public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
- long currentPosMs, float speed) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
- new Long(currentPosMs)).sendToTarget();
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions");
+ if (controllers.size() > 0) {
+ updateCurrentMediaController(controllers.get(0));
}
}
+ }
- @Override
- public void onClientTransportControlUpdate(int transportControlFlags) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, 0, transportControlFlags)
- .sendToTarget();
- }
+ private void updateCurrentMediaController(MediaController controller) {
+ Log.v(TAG, "Updating media controller to " + controller);
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mMediaControllerCb);
}
-
- @Override
- public void onClientMetadataUpdate(MetadataEditor metadataEditor) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_METADATA, 0, 0, metadataEditor).sendToTarget();
- }
+ mMediaController = controller;
+ if (mMediaController == null) {
+ updateMetadata(null);
+ updatePlayPauseState(null);
+ return;
}
+ mMediaController.registerCallback(mMediaControllerCb, mHandler);
+ updateMetadata(mMediaController.getMetadata());
+ updatePlayPauseState(mMediaController.getPlaybackState());
}
/** Handles Avrcp messages. */
@@ -262,28 +286,20 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_UPDATE_STATE:
- updatePlayPauseState(msg.arg2, ((Long) msg.obj).longValue());
- break;
-
- case MSG_SET_METADATA:
- updateMetadata((MetadataEditor) msg.obj);
- break;
-
- case MSG_SET_TRANSPORT_CONTROLS:
- updateTransportControls(msg.arg2);
- break;
-
- case MSG_SET_GENERATION_ID:
- if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
- break;
-
case MESSAGE_GET_RC_FEATURES:
String address = (String) msg.obj;
if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
", features="+msg.arg1);
mFeatures = msg.arg1;
+ mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
+ mLastLocalVolume = -1;
+ mRemoteVolume = -1;
+ mLocalVolume = -1;
+ mInitialRemoteVolume = -1;
+ mAddress = address;
+ if (mVolumeMapping != null)
+ mVolumeMapping.clear();
break;
case MESSAGE_GET_PLAY_STATUS:
@@ -293,21 +309,22 @@
break;
case MESSAGE_GET_ELEM_ATTRS:
- {
String[] textArray;
int[] attrIds;
byte numAttr = (byte) msg.arg1;
ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj;
- if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
+ Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
attrIds = new int[numAttr];
textArray = new String[numAttr];
for (int i = 0; i < numAttr; ++i) {
attrIds[i] = attrList.get(i).intValue();
- textArray[i] = getAttributeString(attrIds[i]);
+ textArray[i] = mMediaAttributes.getString(attrIds[i]);
+ Log.v(TAG, "getAttributeString:attrId=" + attrIds[i] +
+ " str=" + textArray[i]);
}
getElementAttrRspNative(numAttr, attrIds, textArray);
break;
- }
+
case MESSAGE_REGISTER_NOTIFICATION:
if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 +
" param=" + msg.arg2);
@@ -321,47 +338,157 @@
break;
case MESSAGE_VOLUME_CHANGED:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED");
+ break;
+ }
+
if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
+ " ctype=" + msg.arg2);
+
+ boolean volAdj = false;
if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
- if (mVolCmdInProgress == false) {
+ if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) {
Log.e(TAG, "Unsolicited response, ignored");
break;
}
removeMessages(MESSAGE_ABS_VOL_TIMEOUT);
- mVolCmdInProgress = false;
+
+ volAdj = mVolCmdAdjustInProgress;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
mAbsVolRetryTimes = 0;
}
- if (mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT ||
- msg.arg2 == AVRC_RSP_CHANGED ||
- msg.arg2 == AVRC_RSP_INTERIM)) {
- byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
- notifyVolumeChanged(absVol);
- mAbsoluteVolume = absVol;
+
+ byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
+ // convert remote volume to local volume
+ int volIndex = convertToAudioStreamVolume(absVol);
+ if (mInitialRemoteVolume == -1) {
+ mInitialRemoteVolume = absVol;
+ if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax && volIndex > mAbsVolThreshold) {
+ if (DEBUG) Log.v(TAG, "remote inital volume too high " + volIndex + ">" + mAbsVolThreshold);
+ Message msg1 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
+ mHandler.sendMessage(msg1);
+ mRemoteVolume = absVol;
+ mLocalVolume = volIndex;
+ break;
+ }
+ }
+
+ if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT ||
+ msg.arg2 == AVRC_RSP_CHANGED ||
+ msg.arg2 == AVRC_RSP_INTERIM)) {
+ /* If the volume has successfully changed */
+ mLocalVolume = volIndex;
+ if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
+ if (mLastLocalVolume != volIndex) {
+ /* remote volume changed more than requested due to
+ * local and remote has different volume steps */
+ if (DEBUG) Log.d(TAG, "Remote returned volume does not match desired volume "
+ + mLastLocalVolume + " vs "
+ + volIndex);
+ mLastLocalVolume = mLocalVolume;
+ }
+ }
+ // remember the remote volume value, as it's the one supported by remote
+ if (volAdj) {
+ synchronized (mVolumeMapping) {
+ mVolumeMapping.put(volIndex, (int)absVol);
+ if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol);
+ }
+ }
+
+ notifyVolumeChanged(mLocalVolume);
+ mRemoteVolume = absVol;
long pecentVolChanged = ((long)absVol * 100) / 0x7f;
Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
} else if (msg.arg2 == AVRC_RSP_REJ) {
Log.e(TAG, "setAbsoluteVolume call rejected");
+ } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL &&
+ mLocalVolume == volIndex &&
+ (msg.arg2 == AVRC_RSP_ACCEPT )) {
+ /* oops, the volume is still same, remote does not like the value
+ * retry a volume one step up/down */
+ if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step.");
+ int retry_volume = Math.min(AVRCP_MAX_VOL,
+ Math.max(0, mLastRemoteVolume + mLastDirection));
+ if (setVolumeNative(retry_volume)) {
+ mLastRemoteVolume = retry_volume;
+ sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
+ CMD_TIMEOUT_DELAY);
+ mVolCmdAdjustInProgress = true;
+ }
}
break;
case MESSAGE_ADJUST_VOLUME:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME");
+ break;
+ }
+
if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1);
- if (mVolCmdInProgress) {
+
+ if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
+
+ // Remote device didn't set initial volume. Let's black list it
+ if (mInitialRemoteVolume == -1) {
+ Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+ blackListCurrentDevice();
+ break;
+ }
+
// Wait on verification on volume from device, before changing the volume.
- if (mAbsoluteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
- int setVol = Math.min(AVRCP_MAX_VOL,
- Math.max(0, mAbsoluteVolume + msg.arg1*mVolumeStep));
+ if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
+ int setVol = -1;
+ int targetVolIndex = -1;
+ if (mLocalVolume == 0 && msg.arg1 == -1) {
+ if (DEBUG) Log.w(TAG, "No need to Vol down from 0.");
+ break;
+ }
+ if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) {
+ if (DEBUG) Log.w(TAG, "No need to Vol up from max.");
+ break;
+ }
+
+ targetVolIndex = mLocalVolume + msg.arg1;
+ if (DEBUG) Log.d(TAG, "Adjusting volume to " + targetVolIndex);
+
+ Integer i;
+ synchronized (mVolumeMapping) {
+ i = mVolumeMapping.get(targetVolIndex);
+ }
+
+ if (i != null) {
+ /* if we already know this volume mapping, use it */
+ setVol = i.byteValue();
+ if (setVol == mRemoteVolume) {
+ if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore.");
+ setVol = -1;
+ }
+ if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol);
+ }
+
+ if (setVol == -1) {
+ /* otherwise use phone steps */
+ setVol = Math.min(AVRCP_MAX_VOL,
+ convertToAvrcpVolume(Math.max(0, targetVolIndex)));
+ if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol);
+ }
+
if (setVolumeNative(setVol)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
+ mVolCmdAdjustInProgress = true;
mLastDirection = msg.arg1;
- mLastSetVolume = setVol;
+ mLastRemoteVolume = setVol;
+ mLastLocalVolume = targetVolIndex;
+ } else {
+ if (DEBUG) Log.d(TAG, "setVolumeNative failed");
}
} else {
Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME");
@@ -369,52 +496,75 @@
break;
case MESSAGE_SET_ABSOLUTE_VOLUME:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME");
+ break;
+ }
+
if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME");
- if (mVolCmdInProgress) {
+
+ if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
- if (setVolumeNative(msg.arg1)) {
+
+ // Remote device didn't set initial volume. Let's black list it
+ if (mInitialRemoteVolume == -1) {
+ if (DEBUG) Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+ blackListCurrentDevice();
+ break;
+ }
+
+ int avrcpVolume = convertToAvrcpVolume(msg.arg1);
+ avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+ if (DEBUG) Log.d(TAG, "Setting volume to " + msg.arg1 + "-" + avrcpVolume);
+ if (setVolumeNative(avrcpVolume)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
- mLastSetVolume = msg.arg1;
+ mVolCmdSetInProgress = true;
+ mLastRemoteVolume = avrcpVolume;
+ mLastLocalVolume = msg.arg1;
+ } else {
+ if (DEBUG) Log.d(TAG, "setVolumeNative failed");
}
break;
case MESSAGE_ABS_VOL_TIMEOUT:
if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
- mVolCmdInProgress = false;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
mAbsVolRetryTimes = 0;
+ /* too many volume change failures, black list the device */
+ blackListCurrentDevice();
} else {
mAbsVolRetryTimes += 1;
- if (setVolumeNative(mLastSetVolume)) {
+ if (setVolumeNative(mLastRemoteVolume)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
+ mVolCmdSetInProgress = true;
}
}
break;
case MESSAGE_FAST_FORWARD:
case MESSAGE_REWIND:
- if(msg.what == MESSAGE_FAST_FORWARD) {
- if((mTransportControlFlags &
- RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD) != 0) {
- int keyState = msg.arg1 == KEY_STATE_PRESS ?
- KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
- KeyEvent keyEvent =
- new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
- mRemoteController.sendMediaKeyEvent(keyEvent);
- break;
+ if (msg.what == MESSAGE_FAST_FORWARD) {
+ if ((mCurrentPlayState.getActions() &
+ PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ int keyState = msg.arg1 == KEY_STATE_PRESS ?
+ KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
+ KeyEvent keyEvent =
+ new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+ mMediaController.dispatchMediaButtonEvent(keyEvent);
+ break;
}
- } else if((mTransportControlFlags &
- RemoteControlClient.FLAG_KEY_MEDIA_REWIND) != 0) {
+ } else if ((mCurrentPlayState.getActions() &
+ PlaybackState.ACTION_REWIND) != 0) {
int keyState = msg.arg1 == KEY_STATE_PRESS ?
- KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
+ KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
KeyEvent keyEvent =
- new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND);
- mRemoteController.sendMediaKeyEvent(keyEvent);
+ new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND);
+ mMediaController.dispatchMediaButtonEvent(keyEvent);
break;
}
@@ -472,50 +622,63 @@
boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING);
if (isPlaying != isPlayingState(mCurrentPlayState)) {
/* if a2dp is streaming, check to make sure music is active */
- if ( (isPlaying) && !mAudioManager.isMusicActive())
+ if (isPlaying && !mAudioManager.isMusicActive())
return;
- updatePlayPauseState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING :
- RemoteControlClient.PLAYSTATE_PAUSED,
- RemoteControlClient.PLAYBACK_POSITION_INVALID);
+ PlaybackState.Builder builder = new PlaybackState.Builder();
+ if (isPlaying) {
+ builder.setState(PlaybackState.STATE_PLAYING,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
+ } else {
+ builder.setState(PlaybackState.STATE_PAUSED,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
+ }
+ updatePlayPauseState(builder.build());
}
}
- private void updatePlayPauseState(int state, long currentPosMs) {
+ private void updatePlayPauseState(PlaybackState state) {
if (DEBUG) Log.v(TAG,
- "updatePlayPauseState, old=" + mCurrentPlayState + ", state=" + state);
- boolean oldPosValid = (mCurrentPosMs !=
- RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
+ "updatePlayPauseState: old=" + mCurrentPlayState + ", state=" + state);
+ if (state == null) {
+ state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
+ }
+ boolean oldPosValid = (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN);
int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
int newPlayStatus = convertPlayStateToPlayStatus(state);
- if ((mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) &&
- (mCurrentPlayState != state) && oldPosValid) {
+ if ((mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) &&
+ (mCurrentPlayState != state) && oldPosValid) {
mCurrentPosMs = getPlayPosition();
}
- if (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
- mCurrentPosMs = currentPosMs;
+ if (state.getState() == PlaybackState.STATE_NONE ||
+ state.getState() == PlaybackState.STATE_ERROR) {
+ mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ } else {
+ mCurrentPosMs = state.getPosition();
}
- if ((state == RemoteControlClient.PLAYSTATE_PLAYING) &&
- ((currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) ||
- (mCurrentPlayState != RemoteControlClient.PLAYSTATE_PLAYING))) {
+
+ if ((state.getState() == PlaybackState.STATE_PLAYING) &&
+ (mCurrentPlayState.getState() != PlaybackState.STATE_PLAYING)) {
mPlayStartTimeMs = SystemClock.elapsedRealtime();
}
+
mCurrentPlayState = state;
- boolean newPosValid = (mCurrentPosMs !=
- RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
+ boolean newPosValid = mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN;
long playPosition = getPlayPosition();
+
mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
/* need send play position changed notification when play status is changed */
if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) &&
- ((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) ||
- (newPosValid && ((playPosition >= mNextPosMs) || (playPosition <= mPrevPosMs))))) {
+ ((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) ||
+ (newPosValid && ((playPosition >= mNextPosMs) || (playPosition <= mPrevPosMs))))) {
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)playPosition);
}
if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid &&
- (state == RemoteControlClient.PLAYSTATE_PLAYING)) {
+ (state.getState() == PlaybackState.STATE_PLAYING)) {
Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
mHandler.sendMessageDelayed(msg, mNextPosMs - playPosition);
}
@@ -530,54 +693,145 @@
mTransportControlFlags = transportControlFlags;
}
- class Metadata {
- private String artist;
- private String trackTitle;
- private String albumTitle;
+ class MediaAttributes {
+ private boolean exists;
+ private String title;
+ private String artistName;
+ private String albumName;
+ private String mediaNumber;
+ private String mediaTotalNumber;
+ private String genre;
+ private String playingTimeMs;
- public Metadata() {
- artist = null;
- trackTitle = null;
- albumTitle = null;
+ private static final int ATTR_TITLE = 1;
+ private static final int ATTR_ARTIST_NAME = 2;
+ private static final int ATTR_ALBUM_NAME = 3;
+ private static final int ATTR_MEDIA_NUMBER = 4;
+ 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;
+
+
+ public MediaAttributes(MediaMetadata data) {
+ exists = data != null;
+ if (!exists)
+ return;
+
+ artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST));
+ albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM));
+ mediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+ mediaTotalNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
+ genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
+ playingTimeMs = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_DURATION));
+
+ // Try harder for the title.
+ title = data.getString(MediaMetadata.METADATA_KEY_TITLE);
+
+ if (title == null) {
+ MediaDescription desc = data.getDescription();
+ if (desc != null) {
+ CharSequence val = desc.getDescription();
+ if (val != null)
+ title = val.toString();
+ }
+ }
+
+ if (title == null)
+ title = new String();
+ }
+
+ public boolean equals(MediaAttributes other) {
+ if (other == null)
+ return false;
+
+ if (exists != other.exists)
+ return false;
+
+ if (exists == false)
+ return true;
+
+ return (title.equals(other.title)) &&
+ (artistName.equals(other.artistName)) &&
+ (albumName.equals(other.albumName)) &&
+ (mediaNumber.equals(other.mediaNumber)) &&
+ (mediaTotalNumber.equals(other.mediaTotalNumber)) &&
+ (genre.equals(other.genre)) &&
+ (playingTimeMs.equals(other.playingTimeMs));
+ }
+
+ public String getString(int attrId) {
+ if (!exists)
+ return new String();
+
+ switch (attrId) {
+ case ATTR_TITLE:
+ return title;
+ case ATTR_ARTIST_NAME:
+ return artistName;
+ case ATTR_ALBUM_NAME:
+ return albumName;
+ case ATTR_MEDIA_NUMBER:
+ return mediaNumber;
+ case ATTR_MEDIA_TOTAL_NUMBER:
+ return mediaTotalNumber;
+ case ATTR_GENRE:
+ return genre;
+ case ATTR_PLAYING_TIME_MS:
+ return playingTimeMs;
+ default:
+ return new String();
+ }
+ }
+
+ private String stringOrBlank(String s) {
+ return s == null ? new String() : s;
+ }
+
+ private String longStringOrBlank(Long s) {
+ return s == null ? new String() : s.toString();
}
public String toString() {
- return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" +
- albumTitle + "]";
+ if (!exists)
+ return "[MediaAttributes: none]";
+
+ return "[MediaAttributes: " + title + " - " + albumName + " by "
+ + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") "
+ + genre + "]";
}
}
- private void updateMetadata(MetadataEditor data) {
- String oldMetadata = mMetadata.toString();
- mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, null);
- mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null);
- mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null);
- if (!oldMetadata.equals(mMetadata.toString())) {
+ private void updateMetadata(MediaMetadata data) {
+ MediaAttributes oldAttributes = mMediaAttributes;
+ mMediaAttributes = new MediaAttributes(data);
+ if (data == null) {
+ mSongLengthMs = 0L;
+ } else {
+ mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ if (!oldAttributes.equals(mMediaAttributes)) {
+ Log.v(TAG, "MediaAttributes Changed to " + mMediaAttributes.toString());
mTrackNumber++;
if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) {
mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
sendTrackChangedRsp();
}
- if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- mCurrentPosMs = 0L;
- if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
- mPlayStartTimeMs = SystemClock.elapsedRealtime();
- }
+ if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN &&
+ isPlayingState(mCurrentPlayState)) {
+ mPlayStartTimeMs = SystemClock.elapsedRealtime();
}
/* need send play position changed notification when track is changed */
if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) {
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT,
- (int)getPlayPosition());
+ (int)getPlayPosition());
mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
}
+ } else {
+ Log.v(TAG, "Updated " + mMediaAttributes.toString() + " but no change!");
}
- if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString());
- mSongLengthMs = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
- RemoteControlClient.PLAYBACK_POSITION_INVALID);
- if (DEBUG) Log.v(TAG, "duration=" + mSongLengthMs);
}
private void getRcFeatures(byte[] address, int features) {
@@ -611,7 +865,7 @@
case EVT_PLAY_STATUS_CHANGED:
mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM;
registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
- convertPlayStateToPlayStatus(mCurrentPlayState));
+ convertPlayStateToPlayStatus(mCurrentPlayState));
break;
case EVT_TRACK_CHANGED:
@@ -623,10 +877,10 @@
long songPosition = getPlayPosition();
mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM;
mPlaybackIntervalMs = (long)param * 1000L;
- if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
+ if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
mNextPosMs = songPosition + mPlaybackIntervalMs;
mPrevPosMs = songPosition - mPlaybackIntervalMs;
- if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
+ if (isPlayingState(mCurrentPlayState)) {
Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
mHandler.sendMessageDelayed(msg, mPlaybackIntervalMs);
}
@@ -662,7 +916,7 @@
long currentPosMs = getPlayPosition();
if (currentPosMs == -1L) return;
long newPosMs = Math.max(0L, currentPosMs + amount);
- mRemoteController.seekTo(newPosMs);
+ mMediaController.getTransportControls().seekTo(newPosMs);
}
private int getSkipMultiplier() {
@@ -678,7 +932,7 @@
0xFFFFFFFFFFFFFFFF in the interim response */
long trackNumberRsp = -1L;
- if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
+ if (isPlayingState(mCurrentPlayState)) {
trackNumberRsp = mTrackNumber;
}
@@ -691,10 +945,10 @@
private long getPlayPosition() {
long songPosition = -1L;
- if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
+ if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
+ if (mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) {
songPosition = SystemClock.elapsedRealtime() -
- mPlayStartTimeMs + mCurrentPosMs;
+ mPlayStartTimeMs + mCurrentPosMs;
} else {
songPosition = mCurrentPosMs;
}
@@ -703,63 +957,35 @@
return songPosition;
}
- private String getAttributeString(int attrId) {
- String attrStr = null;
- switch (attrId) {
- case MEDIA_ATTR_TITLE:
- attrStr = mMetadata.trackTitle;
- break;
-
- case MEDIA_ATTR_ARTIST:
- attrStr = mMetadata.artist;
- break;
-
- case MEDIA_ATTR_ALBUM:
- attrStr = mMetadata.albumTitle;
- break;
-
- case MEDIA_ATTR_PLAYING_TIME:
- if (mSongLengthMs != 0L) {
- attrStr = Long.toString(mSongLengthMs);
- }
- break;
-
- }
- if (attrStr == null) {
- attrStr = new String();
- }
- if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr);
- return attrStr;
- }
-
- private int convertPlayStateToPlayStatus(int playState) {
+ private int convertPlayStateToPlayStatus(PlaybackState state) {
int playStatus = PLAYSTATUS_ERROR;
- switch (playState) {
- case RemoteControlClient.PLAYSTATE_PLAYING:
- case RemoteControlClient.PLAYSTATE_BUFFERING:
+ switch (state.getState()) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_BUFFERING:
playStatus = PLAYSTATUS_PLAYING;
break;
- case RemoteControlClient.PLAYSTATE_STOPPED:
- case RemoteControlClient.PLAYSTATE_NONE:
+ case PlaybackState.STATE_STOPPED:
+ case PlaybackState.STATE_NONE:
playStatus = PLAYSTATUS_STOPPED;
break;
- case RemoteControlClient.PLAYSTATE_PAUSED:
+ case PlaybackState.STATE_PAUSED:
playStatus = PLAYSTATUS_PAUSED;
break;
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
playStatus = PLAYSTATUS_FWD_SEEK;
break;
- case RemoteControlClient.PLAYSTATE_REWINDING:
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
playStatus = PLAYSTATUS_REV_SEEK;
break;
- case RemoteControlClient.PLAYSTATE_ERROR:
+ case PlaybackState.STATE_ERROR:
playStatus = PLAYSTATUS_ERROR;
break;
@@ -767,18 +993,9 @@
return playStatus;
}
- private boolean isPlayingState(int playState) {
- boolean isPlaying = false;
- switch (playState) {
- case RemoteControlClient.PLAYSTATE_PLAYING:
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- isPlaying = true;
- break;
- default:
- isPlaying = false;
- break;
- }
- return isPlaying;
+ private boolean isPlayingState(PlaybackState state) {
+ return (state.getState() == PlaybackState.STATE_PLAYING) ||
+ (state.getState() == PlaybackState.STATE_BUFFERING);
}
/**
@@ -799,10 +1016,13 @@
}
public void setAbsoluteVolume(int volume) {
- int avrcpVolume = convertToAvrcpVolume(volume);
- avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+ if (volume == mLocalVolume) {
+ if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume);
+ return;
+ }
+
mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
- Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0);
+ Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0);
mHandler.sendMessage(msg);
}
@@ -819,20 +1039,50 @@
}
private void notifyVolumeChanged(int volume) {
- volume = convertToAudioStreamVolume(volume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
private int convertToAudioStreamVolume(int volume) {
// Rescale volume to match AudioSystem's volume
- return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
+ return (int) Math.floor((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
}
private int convertToAvrcpVolume(int volume) {
return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax);
}
+ private void blackListCurrentDevice() {
+ mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+ mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported());
+
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.putBoolean(mAddress, true);
+ editor.commit();
+ }
+
+ private int modifyRcFeatureFromBlacklist(int feature, String address) {
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ if (!pref.contains(address)) {
+ return feature;
+ }
+ if (pref.getBoolean(address, false)) {
+ feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+ }
+ return feature;
+ }
+
+ public void resetBlackList(String address) {
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.remove(address);
+ editor.commit();
+ }
+
/**
* This is called from A2dpStateMachine to set A2dp audio state.
*/
@@ -843,7 +1093,7 @@
public void dump(StringBuilder sb) {
sb.append("AVRCP:\n");
- ProfileService.println(sb, "mMetadata: " + mMetadata);
+ ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes);
ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
@@ -858,14 +1108,16 @@
ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
ProfileService.println(sb, "mFeatures: " + mFeatures);
- ProfileService.println(sb, "mAbsoluteVolume: " + mAbsoluteVolume);
- ProfileService.println(sb, "mLastSetVolume: " + mLastSetVolume);
+ ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
+ ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
ProfileService.println(sb, "mLastDirection: " + mLastDirection);
ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
- ProfileService.println(sb, "mVolCmdInProgress: " + mVolCmdInProgress);
+ ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
+ ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
+ ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
}
// Do not modify without updating the HAL bt_rc.h files.
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java b/src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java
new file mode 100644
index 0000000..422bcfe
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpControllerClasses.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import com.android.bluetooth.Utils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.HashMap;
+import android.util.Log;
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
+import android.media.session.PlaybackState;
+import android.media.MediaMetadata;
+/**
+ * Provides helper classes used by other AvrcpControllerClasses.
+ */
+class AvrcpUtils {
+
+ private static final String TAG = "AvrcpUtils";
+ /*
+ * First 2 apis are utility functions to converts values from AvrcpPlayerSettings defined
+ * in BluetoothAvrcpPlayerSettings to BT spec defined Id and Vals.
+ */
+ public static int mapAttribIdValtoAvrcpPlayerSetting( byte attribId, byte attribVal) {
+ if(AvrcpControllerConstants.VDBG) Log.d(TAG, "attribId: " + attribId + " attribVal: " + attribVal);
+ if (attribId == AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS) {
+ switch(attribVal) {
+ case AvrcpControllerConstants.EQUALIZER_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ case AvrcpControllerConstants.EQUALIZER_STATUS_ON:
+ return BluetoothAvrcpPlayerSettings.STATE_ON;
+ }
+ }
+ else if (attribId == AvrcpControllerConstants.ATTRIB_REPEAT_STATUS) {
+ switch(attribVal) {
+ case AvrcpControllerConstants.REPEAT_STATUS_ALL_TRACK_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case AvrcpControllerConstants.REPEAT_STATUS_GROUP_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case AvrcpControllerConstants.REPEAT_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ case AvrcpControllerConstants.REPEAT_STATUS_SINGLE_TRACK_REPEAT:
+ return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+ }
+ }
+ else if (attribId == AvrcpControllerConstants.ATTRIB_SCAN_STATUS) {
+ switch(attribVal) {
+ case AvrcpControllerConstants.SCAN_STATUS_ALL_TRACK_SCAN:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case AvrcpControllerConstants.SCAN_STATUS_GROUP_SCAN:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case AvrcpControllerConstants.SCAN_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ }
+ }
+ else if (attribId == AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS) {
+ switch(attribVal) {
+ case AvrcpControllerConstants.SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
+ return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+ case AvrcpControllerConstants.SHUFFLE_STATUS_GROUP_SHUFFLE:
+ return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+ case AvrcpControllerConstants.SHUFFLE_STATUS_OFF:
+ return BluetoothAvrcpPlayerSettings.STATE_OFF;
+ }
+ }
+ return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+ }
+ public static int mapAvrcpPlayerSettingstoBTAttribVal(int mSetting, int mSettingVal) {
+ if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return AvrcpControllerConstants.EQUALIZER_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ON:
+ return AvrcpControllerConstants.EQUALIZER_STATUS_ON;
+ }
+ }
+ else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return AvrcpControllerConstants.REPEAT_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
+ return AvrcpControllerConstants.REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return AvrcpControllerConstants.REPEAT_STATUS_ALL_TRACK_REPEAT;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return AvrcpControllerConstants.REPEAT_STATUS_GROUP_REPEAT;
+ }
+ }
+ else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return AvrcpControllerConstants.SHUFFLE_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return AvrcpControllerConstants.SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return AvrcpControllerConstants.SHUFFLE_STATUS_GROUP_SHUFFLE;
+ }
+ }
+ else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
+ switch(mSettingVal) {
+ case BluetoothAvrcpPlayerSettings.STATE_OFF:
+ return AvrcpControllerConstants.SCAN_STATUS_OFF;
+ case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+ return AvrcpControllerConstants.SCAN_STATUS_ALL_TRACK_SCAN;
+ case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+ return AvrcpControllerConstants.SCAN_STATUS_GROUP_SCAN;
+ }
+ }
+ return AvrcpControllerConstants.STATUS_INVALID;
+ }
+ /*
+ * This api converts btPlayStatus to PlaybackState
+ */
+ public static PlaybackState mapBtPlayStatustoPlayBackState(byte btPlayStatus, long btPlayPos) {
+ int mState = PlaybackState.STATE_NONE;
+ long position = btPlayPos;
+ float speed = 1;
+ switch(btPlayStatus) {
+ case AvrcpControllerConstants.PLAY_STATUS_STOPPED:
+ mState = PlaybackState.STATE_STOPPED;
+ position = 0;
+ speed = 0;
+ break;
+ case AvrcpControllerConstants.PLAY_STATUS_PLAYING:
+ mState = PlaybackState.STATE_PLAYING;
+ break;
+ case AvrcpControllerConstants.PLAY_STATUS_PAUSED:
+ mState = PlaybackState.STATE_PAUSED;
+ speed = 0;
+ break;
+ case AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK:
+ mState = PlaybackState.STATE_FAST_FORWARDING;
+ speed = 3;
+ break;
+ case AvrcpControllerConstants.PLAY_STATUS_REV_SEEK:
+ mState = PlaybackState.STATE_REWINDING;
+ speed = -3;
+ break;
+ }
+ return new PlaybackState.Builder().setState(mState, position, speed).build();
+ }
+ /*
+ * This api converts meta info into MediaMetaData
+ */
+ public static MediaMetadata getMediaMetaData(TrackInfo mTrackInfo) {
+ if(AvrcpControllerConstants.VDBG) Log.d(TAG, " TrackInfo " + mTrackInfo.toString());
+ MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,
+ mTrackInfo.mArtistName);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,
+ mTrackInfo.mTrackTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM,
+ mTrackInfo.mAlbumTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE,
+ mTrackInfo.mGenre);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+ mTrackInfo.mTrackNum);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+ mTrackInfo.mTotalTracks);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+ mTrackInfo.mTrackLen);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID,
+ String.valueOf(mTrackInfo.mItemUid));
+ return mMetaDataBuilder.build();
+ }
+ /*
+ * Display Apis
+ */
+ public static String displayMetaData(MediaMetadata mMetaData) {
+ StringBuffer sb = new StringBuffer();
+ /* this will only show artist, title and album */
+ sb.append(mMetaData.getDescription().toString() + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_GENRE))
+ sb.append(mMetaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID))
+ sb.append(mMetaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
+ sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ if(mMetaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+ sb.append(Long.toString(mMetaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ return sb.toString();
+ }
+ public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
+ StringBuffer sb = new StringBuffer();
+ int supportedSetting = mSett.getSettings();
+ if(AvrcpControllerConstants.VDBG) Log.d(TAG," setting: " + supportedSetting);
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+ sb.append(" EQ : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_EQUALIZER)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+ sb.append(" REPEAT : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_REPEAT)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+ sb.append(" SHUFFLE : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_SHUFFLE)));
+ }
+ if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+ sb.append(" SCAN : ");
+ sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+ SETTING_SCAN)));
+ }
+ return sb.toString();
+ }
+}
+/*
+ * Contains information about remote device
+ */
+class RemoteDevice {
+ BluetoothDevice mBTDevice;
+ int mRemoteFeatures;
+ int mBatteryStatus;
+ int mSystemStatus;
+ int mAbsVolNotificationState;
+ int mNotificationLabel;
+ boolean mFirstAbsVolCmdRecvd;
+
+ public void cleanup() {
+ mBTDevice = null;
+ mRemoteFeatures = AvrcpControllerConstants.BTRC_FEAT_NONE;
+ mBatteryStatus = AvrcpControllerConstants.BATT_POWER_UNDEFINED;
+ mSystemStatus = AvrcpControllerConstants.SYSTEM_STATUS_UNDEFINED;
+ mAbsVolNotificationState = AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+ mNotificationLabel = AvrcpControllerConstants.VOLUME_LABEL_UNDEFINED;
+ mFirstAbsVolCmdRecvd = false;
+ }
+
+ public RemoteDevice(BluetoothDevice mDevice) {
+ mBTDevice = mDevice;
+ mRemoteFeatures = AvrcpControllerConstants.BTRC_FEAT_NONE;
+ mBatteryStatus = AvrcpControllerConstants.BATT_POWER_UNDEFINED;
+ mSystemStatus = AvrcpControllerConstants.SYSTEM_STATUS_UNDEFINED;
+ mAbsVolNotificationState = AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+ mNotificationLabel = AvrcpControllerConstants.VOLUME_LABEL_UNDEFINED;
+ mFirstAbsVolCmdRecvd = false;
+ }
+
+ public boolean isBrowsingSupported() {
+ if((mRemoteFeatures & AvrcpControllerConstants.BTRC_FEAT_BROWSE) != 0)
+ return true;
+ else
+ return false;
+ }
+ public boolean isMetaDataSupported() {
+ if((mRemoteFeatures & AvrcpControllerConstants.BTRC_FEAT_METADATA) != 0)
+ return true;
+ else
+ return false;
+ }
+}
+
+/*
+ * Base Class for Media Item
+ */
+class MediaItem {
+ /*
+ * This is a combination of locaiton and item. Spec Snippet
+ * In VFS if same item is in different location it may have same uid.
+ * In Now Playing same item should have differnt UID
+ * Can never be 0, used only for GetElementAttributes
+ * TODO: UID counter, which is used for database aware player
+ */
+ double mItemUid;
+}
+
+/*
+ * Contains information Player Application Setting
+ */
+class PlayerApplicationSettings {
+ public byte attr_Id;
+ public byte attr_val;
+ public byte [] supported_values;
+ public String attr_text;
+ public String [] supported_values_text;// This is to keep displayable text in UTF-8
+}
+/*
+ * Contains information about remote player
+ */
+class PlayerInfo {
+ private static final String TAG = "PlayerInfo";
+ byte mPlayStatus;
+ long mPlayTime;
+ /*
+ * 2 byte player id to identify player.
+ * In 1.3 this value will be set to zero
+ */
+ char mPlayerId;
+ ArrayList<PlayerApplicationSettings> mPlayerAppSetting;
+ private void resetPlayer() {
+ mPlayStatus = AvrcpControllerConstants.PLAY_STATUS_STOPPED;
+ mPlayTime = AvrcpControllerConstants.PLAYING_TIME_INVALID;
+ mPlayerId = 0;
+ mPlayerAppSetting = new ArrayList<PlayerApplicationSettings>();
+ }
+ public PlayerInfo() {
+ resetPlayer();
+ }
+ public void setSupportedPlayerAppSetting (ByteBuffer bb) {
+ /* ByteBuffer has to be of the following format
+ * id, num_values, values[]
+ */
+ while(bb.hasRemaining()) {
+ PlayerApplicationSettings plAppSetting = new PlayerApplicationSettings();
+ plAppSetting.attr_Id = bb.get();
+ byte numSupportedVals = bb.get();
+ plAppSetting.supported_values = new byte[numSupportedVals];
+ for (int i = 0; i<numSupportedVals; i++) {
+ plAppSetting.supported_values[i] = bb.get();
+ }
+ mPlayerAppSetting.add(plAppSetting);
+ }
+ }
+ public void updatePlayerAppSetting(ByteBuffer bb) {
+ /* ByteBuffer has to be of the following format
+ * <id, value>
+ */
+ if(mPlayerAppSetting.isEmpty())
+ return;
+ while(bb.hasRemaining()) {
+ byte attribId = bb.get();
+ for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+ if(plAppSetting.attr_Id == attribId)
+ plAppSetting.attr_val = bb.get();
+ }
+ }
+ }
+
+ public BluetoothAvrcpPlayerSettings getSupportedPlayerAppSetting() {
+ /*
+ * Here we create PlayerAppSetting
+ * based on BluetoothAvrcpPlayerSettings
+ */
+ int supportedSettings = 0; // Player App Setting used by BluetoothAvrcpPlayerSettings.
+ for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+ switch(plAppSetting.attr_Id) {
+ case AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS:
+ supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
+ break;
+ case AvrcpControllerConstants.ATTRIB_REPEAT_STATUS:
+ supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
+ break;
+ case AvrcpControllerConstants.ATTRIB_SCAN_STATUS:
+ supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_SCAN;
+ break;
+ case AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS:
+ supportedSettings |= BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
+ break;
+ }
+ }
+ BluetoothAvrcpPlayerSettings mAvrcpPlayerAppSetting = new
+ BluetoothAvrcpPlayerSettings(supportedSettings);
+ for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+ switch(plAppSetting.attr_Id) {
+ case AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS:
+ mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER,
+ AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+ plAppSetting.attr_val));
+ break;
+ case AvrcpControllerConstants.ATTRIB_REPEAT_STATUS:
+ mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT,
+ AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+ plAppSetting.attr_val));
+ break;
+ case AvrcpControllerConstants.ATTRIB_SCAN_STATUS:
+ mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN,
+ AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+ plAppSetting.attr_val));
+ break;
+ case AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS:
+ mAvrcpPlayerAppSetting.addSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE,
+ AvrcpUtils.mapAttribIdValtoAvrcpPlayerSetting(plAppSetting.attr_Id,
+ plAppSetting.attr_val));
+ break;
+ }
+ }
+ return mAvrcpPlayerAppSetting;
+ }
+ public byte getCurrentPlayerAppSettingValue(byte mPlayerAppAttrId) {
+ for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+ if(mPlayerAppAttrId == plAppSetting.attr_Id)
+ return plAppSetting.attr_val;
+ }
+ return 0;
+ }
+ /*
+ * Checks if current setting is supported by remote.
+ * input would be in form of flattened strucuture <id,val>
+ */
+ public boolean isPlayerAppSettingSupported(byte numAttributes, byte[] playerAppSetting) {
+ for( int i = 0; (i < 2*numAttributes);) {
+ byte id = playerAppSetting[i++];
+ byte val = playerAppSetting[i++];
+ boolean found = false;
+ for(PlayerApplicationSettings plAppSetting: mPlayerAppSetting) {
+ if(plAppSetting.attr_Id == id) {
+ for(int j = 0; j < plAppSetting.supported_values.length; j++) {
+ if(val == plAppSetting.supported_values[j]) {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ if(!found)
+ return false;
+ }
+ return true;
+ }
+}
+
+/*
+ * Contains information about track
+ */
+class TrackInfo extends MediaItem {
+ String mArtistName;
+ String mTrackTitle;
+ String mAlbumTitle;
+ String mGenre;
+ long mTrackNum; // number of audio file on original recording.
+ long mTotalTracks;// total number of tracks on original recording
+ long mTrackLen;// full length of AudioFile.
+ /* In case of 1.3 we have to set itemUid explicitly to 0 */
+
+ /* reset it to default values */
+ private void resetTrackInfo() {
+ mArtistName = AvrcpControllerConstants.ARTIST_NAME_INVALID;;
+ mTrackTitle = AvrcpControllerConstants.TITLE_INVALID;;
+ mAlbumTitle = AvrcpControllerConstants.ALBUM_NAME_INVALID;
+ mGenre = AvrcpControllerConstants.GENRE_INVALID;
+ mTrackNum = AvrcpControllerConstants.TRACK_NUM_INVALID;
+ mTotalTracks = AvrcpControllerConstants.TOTAL_TRACK_TIME_INVALID;
+ mTrackLen = AvrcpControllerConstants.TOTAL_TRACK_TIME_INVALID;
+ }
+ public TrackInfo() {
+ resetTrackInfo();
+ }
+ public TrackInfo(int mTrackId, byte mNumAttributes, int[] mAttribIds, String[] mAttribs) {
+ mItemUid = mTrackId;
+ resetTrackInfo();
+ for (int i = 0; i < mNumAttributes; i++) {
+ switch(mAttribIds[i]) {
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TITLE:
+ mTrackTitle = mAttribs[i];
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_ARTIST_NAME:
+ mArtistName = mAttribs[i];
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_ALBUM_NAME:
+ mAlbumTitle = mAttribs[i];
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ if(!mAttribs[i].isEmpty())
+ mTrackNum = Long.valueOf(mAttribs[i]);
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ if(!mAttribs[i].isEmpty())
+ mTotalTracks = Long.valueOf(mAttribs[i]);
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_GENRE:
+ mGenre = mAttribs[i];
+ break;
+ case AvrcpControllerConstants.MEDIA_ATTRIBUTE_PLAYING_TIME:
+ if(!mAttribs[i].isEmpty())
+ mTrackLen = Long.valueOf(mAttribs[i]);
+ break;
+ }
+ }
+ }
+ public String toString() {
+ return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle +
+ " albumTitle= " + mAlbumTitle + " genre= " +mGenre+" trackNum= "+
+ Long.toString(mTrackNum) + " track_len : "+ Long.toString(mTrackLen) +
+ " TotalTracks " + Long.toString(mTotalTracks) + "]";
+ }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java b/src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java
new file mode 100644
index 0000000..d600474
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpControllerConstants.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides helper classes used by other AvrcpControllerClasses.
+ * Don't change this file without changing HAL Constants in bt_rc.h
+ */
+
+final class AvrcpControllerConstants {
+
+ /*
+ * Debug flags
+ */
+ public static final boolean DBG = true;
+ public static final boolean VDBG = true;
+ /*
+ * Whether to push broadcast updates about metadata.
+ */
+ public static final int START_METADATA_BROADCASTS = 0;
+ public static final int STOP_METADATA_BROADCASTS = 1;
+
+ /*
+ * Scopes of operation
+ */
+ public static final int AVRCP_SCOPE_NOW_PLAYING = 0;
+ public static final int AVRCP_SCOPE_VFS = 1;
+ /*
+ * Remote features
+ */
+ public static final byte BTRC_FEAT_NONE = 0;
+ public static final byte BTRC_FEAT_METADATA = 1;
+ public static final byte BTRC_FEAT_ABSOLUTE_VOLUME = 2;
+ public static final byte BTRC_FEAT_BROWSE = 4;
+
+ /*
+ *Element Id Values for GetMetaData
+ */
+ public static final int MEDIA_ATTRIBUTE_TITLE = 0x01;
+ public static final int MEDIA_ATTRIBUTE_ARTIST_NAME = 0x02;
+ public static final int MEDIA_ATTRIBUTE_ALBUM_NAME = 0x03;
+ public static final int MEDIA_ATTRIBUTE_TRACK_NUMBER = 0x04;
+ public static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
+ public static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
+ public static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+
+ /*
+ * Default values for each of the items
+ */
+ public static final int TRACK_NUM_INVALID = 0xFF;
+ public static final int STATUS_INVALID = 0xFF;
+ public static final String TITLE_INVALID = "NOT_SUPPORTED";
+ public static final String ARTIST_NAME_INVALID = "NOT_SUPPORTED";
+ public static final String ALBUM_NAME_INVALID = "NOT_SUPPORTED";
+ public static final int TOTAL_TRACKS_INVALID = 0xFFFFFFFF;
+ public static final String GENRE_INVALID = "NOT_SUPPORTED";
+ public static final int PLAYING_TIME_INVALID = 0xFF;
+ public static final int TOTAL_TRACK_TIME_INVALID = 0xFF;
+ public static final String REPEAT_STATUS_INVALID = "NOT_SUPPORTED";
+ public static final String SHUFFLE_STATUS_INVALID = "NOT_SUPPORTED";
+ public static final String SCAN_STATUS_INVALID = "NOT_SUPPORTED";
+ public static final String EQUALIZER_STATUS_INVALID = "NOT_SUPPORTED";
+ public static final String BATTERY_STATUS_INVALID = "NOT_SUPPORTED";
+ public static final String SYSTEM_STATUS_INVALID = "NOT_SUPPORTED";
+
+ /*
+ * Values for SetPlayerApplicationSettings
+ */
+ public static final byte ATTRIB_EQUALIZER_STATUS = 0x01;
+ public static final byte ATTRIB_REPEAT_STATUS = 0x02;
+ public static final byte ATTRIB_SHUFFLE_STATUS = 0x03;
+ public static final byte ATTRIB_SCAN_STATUS = 0x04;
+
+ public static final byte EQUALIZER_STATUS_OFF = 0x01;
+ public static final byte EQUALIZER_STATUS_ON = 0x02;
+
+ public static final byte REPEAT_STATUS_OFF = 0x01;
+ public static final byte REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+ public static final byte REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+ public static final byte REPEAT_STATUS_GROUP_REPEAT = 0x04;
+
+ public static final byte SHUFFLE_STATUS_OFF = 0x01;
+ public static final byte SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+ public static final byte SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+
+ public static final byte SCAN_STATUS_OFF = 0x01;
+ public static final byte SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
+ public static final byte SCAN_STATUS_GROUP_SCAN = 0x03;
+
+ /*
+ * Play State Values
+ */
+ public static final int PLAY_STATUS_STOPPED = 0x00;
+ public static final int PLAY_STATUS_PLAYING = 0x01;
+ public static final int PLAY_STATUS_PAUSED = 0x02;
+ public static final int PLAY_STATUS_FWD_SEEK = 0x03;
+ public static final int PLAY_STATUS_REV_SEEK = 0x04;
+ public static final int PLAY_STATUS_ERROR = 0xFF;
+ /*
+ * System Status
+ */
+ public static final int SYSTEM_POWER_ON = 0x00;
+ public static final int SYSTEM_POWER_OFF = 0x01;
+ public static final int SYSTEM_UNPLUGGED = 0x02;
+ public static final int SYSTEM_STATUS_UNDEFINED = 0xFF;
+ /*
+ * Battery Status
+ */
+ public static final int BATT_POWER_NORMAL = 0x00;
+ public static final int BATT_POWER_WARNING = 0x01;
+ public static final int BATT_POWER_CRITICAL = 0x02;
+ public static final int BATT_POWER_EXTERNAL = 0x03;
+ public static final int BATT_POWER_FULL_CHARGE = 0x04;
+ public static final int BATT_POWER_UNDEFINED = 0xFF;
+
+ public static final int NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
+ public static final int NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
+ /*
+ * Base value for absolute volume
+ */
+ static final int ABS_VOL_BASE = 127;
+
+ public static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+ public static final int MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS = 2;
+ public static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
+
+ public static final int MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING = 101;
+ public static final int MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED = 102;
+ public static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
+ public static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
+ public static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
+ public static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
+ public static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
+
+ public static final int MESSAGE_PROCESS_RC_FEATURES = 1100;
+ public static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 1200;
+
+ public static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
+ public static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+
+
+ public static String dumpMessageString(int message)
+ {
+ String str = "UNKNOWN";
+ switch(message)
+ {
+ case MESSAGE_SEND_PASS_THROUGH_CMD:
+ str = "REQ_PASS_THROUGH_CMD";
+ break;
+ case MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ str = "REQ_SET_PLAYER_APP_SETTING";
+ break;
+ case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+ str = "REQ_GRP_NAV_CMD";
+ break;
+ case MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
+ str = "CB_SUPPORTED_PLAYER_APP_SETTING";
+ break;
+ case MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
+ str = "CB_PLAYER_APP_SETTING_CHANGED";
+ break;
+ case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ str = "CB_SET_ABS_VOL_CMD";
+ break;
+ case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+ str = "CB_REGISTER_ABS_VOL";
+ break;
+ case MESSAGE_PROCESS_TRACK_CHANGED:
+ str = "CB_TRACK_CHANGED";
+ break;
+ case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ str = "CB_PLAY_POS_CHANGED";
+ break;
+ case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+ str = "CB_PLAY_STATUS_CHANGED";
+ break;
+ case MESSAGE_PROCESS_RC_FEATURES:
+ str = "CB_RC_FEATURES";
+ break;
+ case MESSAGE_PROCESS_CONNECTION_CHANGE:
+ str = "CB_CONN_CHANGED";
+ break;
+ default:
+ str = Integer.toString(message);
+ break;
+ }
+ return str;
+ }
+
+ /* Absolute Volume Notification State */
+ /* if we are in this state, we would send vol update to remote */
+ public static final int SEND_VOLUME_CHANGE_RSP = 0;
+ /* if we are in this state, we would not send vol update to remote */
+ public static final int DEFER_VOLUME_CHANGE_RSP = 1;
+ public static final int VOLUME_LABEL_UNDEFINED = 0xFF;
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
index ed426ec..313764e 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
@@ -18,35 +18,51 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothAvrcpController;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
-
+import android.media.AudioManager;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
-
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
-
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
/**
* Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
* @hide
*/
public class AvrcpControllerService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = AvrcpControllerConstants.DBG;
+ private static final boolean VDBG = AvrcpControllerConstants.VDBG;
private static final String TAG = "AvrcpControllerService";
- private static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+/*
+ * Messages handled by mHandler
+ */
+
+ RemoteDevice mAvrcpRemoteDevice;
+ RemoteMediaPlayers mRemoteMediaPlayers;
+ NowPlaying mRemoteNowPlayingList;
private AvrcpMessageHandler mHandler;
private static AvrcpControllerService sAvrcpControllerService;
+ private static AudioManager mAudioManager;
private final ArrayList<BluetoothDevice> mConnectedDevices
= new ArrayList<BluetoothDevice>();
@@ -74,10 +90,40 @@
mHandler = new AvrcpMessageHandler(looper);
setAvrcpControllerService(this);
+ mAudioManager = (AudioManager)sAvrcpControllerService.
+ getSystemService(Context.AUDIO_SERVICE);
return true;
}
+ protected void resetRemoteData() {
+ try {
+ unregisterReceiver(mBroadcastReceiver);
+ }
+ catch (IllegalArgumentException e) {
+ Log.e(TAG,"Receiver not registered");
+ }
+ if(mAvrcpRemoteDevice != null) {
+ mAvrcpRemoteDevice.cleanup();
+ mAvrcpRemoteDevice = null;
+ }
+ if(mRemoteMediaPlayers != null) {
+ mRemoteMediaPlayers.cleanup();
+ mRemoteMediaPlayers = null;
+ }
+ if(mRemoteNowPlayingList != null) {
+ mRemoteNowPlayingList.cleanup();
+ mRemoteNowPlayingList = null;
+ }
+ }
protected boolean stop() {
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ Looper looper = mHandler.getLooper();
+ if (looper != null) {
+ looper.quit();
+ }
+ }
+ resetRemoteData();
return true;
}
@@ -87,7 +133,7 @@
Looper looper = mHandler.getLooper();
if (looper != null) looper.quit();
}
-
+ resetRemoteData();
clearAvrcpControllerService();
cleanupNative();
@@ -152,18 +198,187 @@
: BluetoothProfile.STATE_DISCONNECTED);
}
- public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- if (DBG) Log.d(TAG, "sendPassThroughCmd");
- Log.v(TAG, "keyCode: " + keyCode + " keyState: " + keyState);
+ public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
if (device == null) {
throw new NullPointerException("device == null");
}
+ if (!(mConnectedDevices.contains(device))) {
+ for (BluetoothDevice cdevice : mConnectedDevices) {
+ Log.e(TAG, "Device: " + cdevice);
+ }
+ Log.e(TAG," Device does not match " + device);
+ return;
+ }
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(MESSAGE_SEND_PASS_THROUGH_CMD,
- keyCode, keyState, device);
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_SEND_GROUP_NAVIGATION_CMD,keyCode, keyState, device);
mHandler.sendMessage(msg);
}
+ public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+ if (device == null) {
+ throw new NullPointerException("device == null");
+ }
+ if (!(mConnectedDevices.contains(device))) {
+ Log.d(TAG," Device does not match");
+ return;
+ }
+ if ((mAvrcpRemoteDevice == null)||
+ (mAvrcpRemoteDevice.mRemoteFeatures == AvrcpControllerConstants.BTRC_FEAT_NONE)||
+ (mRemoteMediaPlayers == null) ||
+ (mRemoteMediaPlayers.getAddressedPlayer() == null)){
+ Log.d(TAG," Device connected but PlayState not present ");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
+ keyCode, keyState, device);
+ mHandler.sendMessage(msg);
+ return;
+ }
+ boolean sendCommand = false;
+ switch(keyCode) {
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY:
+ sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PAUSED) ||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PLAYING);
+ break;
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE:
+ /*
+ * allowing pause command in pause state to handle A2DP Sink Concurrency
+ * If call is ongoing and Start is initiated from remote, we will send pause again
+ * If acquireFocus fails, we will send Pause again
+ * To Stop sending multiple Pause, check in application.
+ */
+ sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PAUSED)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_REV_SEEK);
+ break;
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP:
+ sendCommand = (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_REV_SEEK)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
+ (mRemoteMediaPlayers.getPlayStatus() ==
+ AvrcpControllerConstants.PLAY_STATUS_PAUSED);
+ break;
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD:
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD:
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_FF:
+ case BluetoothAvrcpController.PASS_THRU_CMD_ID_REWIND:
+ sendCommand = true; // we can send this command in all states
+ break;
+ }
+ if (sendCommand) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
+ keyCode, keyState, device);
+ mHandler.sendMessage(msg);
+ }
+ else {
+ Log.e(TAG," Not in right state, don't send Pass Thru cmd ");
+ }
+ }
+
+ public void startAvrcpUpdates() {
+ mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
+ }
+
+ public void stopAvrcpUpdates() {
+ mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
+ }
+
+ public MediaMetadata getMetaData(BluetoothDevice device) {
+ Log.d(TAG, "getMetaData = ");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if((mRemoteNowPlayingList != null) && (mRemoteNowPlayingList.getCurrentTrack() != null)) {
+ return getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0);
+ }
+ else
+ return null;
+ }
+ public PlaybackState getPlaybackState(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getPlayBackState device = "+ device);
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return getCurrentPlayBackState();
+ }
+ public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getPlayerApplicationSetting ");
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return getCurrentPlayerAppSetting();
+ }
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+ if ((mAvrcpRemoteDevice == null)||(mRemoteMediaPlayers == null)) {
+ return false;
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ /*
+ * We have to extract values from BluetoothAvrcpPlayerSettings
+ */
+ int mSettings = plAppSetting.getSettings();
+ int numAttributes = 0;
+ /* calculate number of attributes in request */
+ while(mSettings > 0) {
+ numAttributes += ((mSettings & 0x01)!= 0)?1: 0;
+ mSettings = mSettings >> 1;
+ }
+ byte[] attribArray = new byte [2*numAttributes];
+ mSettings = plAppSetting.getSettings();
+ /*
+ * Now we will flatten it <id, val>
+ */
+ int i = 0;
+ if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+ attribArray[i++] = AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS;
+ attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+ BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER, plAppSetting.
+ getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER));
+ }
+ if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+ attribArray[i++] = AvrcpControllerConstants.ATTRIB_REPEAT_STATUS;
+ attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+ BluetoothAvrcpPlayerSettings.SETTING_REPEAT, plAppSetting.
+ getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT));
+ }
+ if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+ attribArray[i++] = AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS;
+ attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+ BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE, plAppSetting.
+ getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE));
+ }
+ if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+ attribArray[i++] = AvrcpControllerConstants.ATTRIB_SCAN_STATUS;
+ attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
+ BluetoothAvrcpPlayerSettings.SETTING_SCAN, plAppSetting.
+ getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN));
+ }
+ boolean isSettingSupported = mRemoteMediaPlayers.getAddressedPlayer().
+ isPlayerAppSettingSupported((byte)numAttributes, attribArray);
+ if(isSettingSupported) {
+ ByteBuffer bb = ByteBuffer.wrap(attribArray, 0, (2*numAttributes));
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS, numAttributes, 0, bb);
+ mHandler.sendMessage(msg);
+ }
+ return isSettingSupported;
+ }
+
//Binder object: Must be static class or memory leak may occur
private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
@@ -214,49 +429,512 @@
if (service == null) return;
service.sendPassThroughCmd(device, keyCode, keyState);
}
+
+ @Override
+ public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG,"Binder Call: sendGroupNavigationCmd");
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.sendGroupNavigationCmd(device, keyCode, keyState);
+ }
+
+ @Override
+ public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+ Log.v(TAG,"Binder Call: getPlayerApplicationSetting ");
+ AvrcpControllerService service = getService();
+ if (service == null) return null;
+ return service.getPlayerSettings(device);
+ }
+
+ @Override
+ public MediaMetadata getMetadata(BluetoothDevice device) {
+ Log.v(TAG,"Binder Call: getMetaData ");
+ AvrcpControllerService service = getService();
+ if (service == null) return null;
+ return service.getMetaData(device);
+ }
+
+ @Override
+ public PlaybackState getPlaybackState(BluetoothDevice device) {
+ Log.v(TAG,"Binder Call: getPlaybackState");
+ AvrcpControllerService service = getService();
+ if (service == null) return null;
+ return service.getPlaybackState(device);
+ }
+
+ @Override
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+ Log.v(TAG,"Binder Call: setPlayerApplicationSetting " );
+ AvrcpControllerService service = getService();
+ if (service == null) return false;
+ return service.setPlayerApplicationSetting(plAppSetting);
+ }
};
+ private String utf8ToString(byte[] input)
+ {
+ Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ return new String(input,UTF8_CHARSET);
+ }
+ private int asciiToInt(int len, byte[] array)
+ {
+ return Integer.parseInt(utf8ToString(array));
+ }
+ private BluetoothAvrcpPlayerSettings getCurrentPlayerAppSetting() {
+ if((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null))
+ return null;
+ return mRemoteMediaPlayers.getAddressedPlayer().getSupportedPlayerAppSetting();
+ }
+ private PlaybackState getCurrentPlayBackState() {
+ if ((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) {
+ return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ 0).build();
+ }
+ return AvrcpUtils.mapBtPlayStatustoPlayBackState(
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayTime);
+ }
+ private MediaMetadata getCurrentMetaData(int scope, int trackId) {
+ /* if scope is now playing */
+ if(scope == AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING) {
+ if((mRemoteNowPlayingList == null) || (mRemoteNowPlayingList.
+ getTrackFromId(trackId) == null))
+ return null;
+ TrackInfo mNowPlayingTrack = mRemoteNowPlayingList.getTrackFromId(trackId);
+ return AvrcpUtils.getMediaMetaData(mNowPlayingTrack);
+ }
+ /* if scope is now playing */
+ else if(scope == AvrcpControllerConstants.AVRCP_SCOPE_VFS) {
+ /* TODO for browsing */
+ }
+ return null;
+ }
+ private void broadcastMetaDataChanged(MediaMetadata mMetaData) {
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+ intent.putExtra(BluetoothAvrcpController.EXTRA_METADATA, mMetaData);
+ if(DBG) Log.d(TAG," broadcastMetaDataChanged = " +
+ AvrcpUtils.displayMetaData(mMetaData));
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ private void broadcastPlayBackStateChanged(PlaybackState state) {
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+ intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYBACK, state);
+ if(DBG) Log.d(TAG," broadcastPlayBackStateChanged = " + state.toString());
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ private void broadcastPlayerAppSettingChanged(BluetoothAvrcpPlayerSettings mPlAppSetting) {
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
+ intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYER_SETTING, mPlAppSetting);
+ if(DBG) Log.d(TAG," broadcastPlayerAppSettingChanged = " +
+ AvrcpUtils.displayBluetoothAvrcpSettings(mPlAppSetting));
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
/** Handles Avrcp messages. */
private final class AvrcpMessageHandler extends Handler {
+ private boolean mBroadcastMetadata = false;
+
private AvrcpMessageHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
+ Log.d(TAG," HandleMessage: "+ AvrcpControllerConstants.dumpMessageString(msg.what) +
+ " Remote Connected " + !mConnectedDevices.isEmpty());
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
switch (msg.what) {
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- if (DBG) Log.v(TAG, "MESSAGE_SEND_PASS_THROUGH_CMD");
+ case AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS:
+ // Any messages hence forth about play pos/play status/song info will be ignored.
+ if(mRemoteMediaPlayers != null) {
+ // Mock the current state to *look like* it is paused. The remote play state is
+ // still cached in mRemoteMediaPlayers and will be restored when the
+ // startAvrcpUpdates is called again.
+ broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
+ ((byte) AvrcpControllerConstants.PLAY_STATUS_PAUSED,
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
+ }
+ mBroadcastMetadata = false;
+ break;
+ case AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS:
+ // Any messages hence forth about play pos/play status/song info will be sent.
+ if(mRemoteMediaPlayers != null) {
+ broadcastPlayBackStateChanged(getCurrentPlayBackState());
+ broadcastMetaDataChanged(
+ getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0));
+ }
+ mBroadcastMetadata = true;
+ break;
+ case AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD:
BluetoothDevice device = (BluetoothDevice)msg.obj;
sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
+ if((a2dpSinkService != null)&&(!mConnectedDevices.isEmpty())) {
+ Log.d(TAG," inform AVRCP Commands to A2DP Sink ");
+ a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
+ }
+ break;
+ case AvrcpControllerConstants.MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+ BluetoothDevice peerDevice = (BluetoothDevice)msg.obj;
+ sendGroupNavigationCommandNative(getByteAddress(peerDevice), msg.arg1, msg.arg2);
+ break;
+ case AvrcpControllerConstants.MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
+ byte numAttributes = (byte)msg.arg1;
+ ByteBuffer bbRsp = (ByteBuffer)msg.obj;
+ byte[] attributeIds = new byte [numAttributes];
+ byte[] attributeVals = new byte [numAttributes];
+ for(int i = 0; (bbRsp.hasRemaining())&&(i < numAttributes); i++) {
+ attributeIds[i] = bbRsp.get();
+ attributeVals[i] = bbRsp.get();
+ }
+ setPlayerApplicationSettingValuesNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+ numAttributes, attributeIds, attributeVals);
+ break;
+
+ case AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE:
+ int newState = msg.arg1;
+ int oldState = msg.arg2;
+ BluetoothDevice rtDevice = (BluetoothDevice)msg.obj;
+ if ((newState == BluetoothProfile.STATE_CONNECTED) &&
+ (oldState == BluetoothProfile.STATE_DISCONNECTED)) {
+ /* We create RemoteDevice and MediaPlayerList here
+ * Now playing list after RC features
+ */
+ if(mAvrcpRemoteDevice == null){
+ mAvrcpRemoteDevice = new RemoteDevice(rtDevice);
+ /* Remote will have a player irrespective of AVRCP Version
+ * Create a Default player, we will add entries in case Browsing
+ * is supported by remote
+ */
+ if(mRemoteMediaPlayers == null) {
+ mRemoteMediaPlayers = new RemoteMediaPlayers(mAvrcpRemoteDevice);
+ PlayerInfo mPlayer = new PlayerInfo();
+ mPlayer.mPlayerId = 0;
+ mRemoteMediaPlayers.addPlayer(mPlayer);
+ mRemoteMediaPlayers.setAddressedPlayer(mPlayer);
+ }
+ }
+ }
+ else if ((newState == BluetoothProfile.STATE_DISCONNECTED) &&
+ (oldState == BluetoothProfile.STATE_CONNECTED)) /* connection down */
+ {
+ resetRemoteData();
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ /*
+ * Send intent now
+ */
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+// intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES:
+ if(mAvrcpRemoteDevice == null)
+ break;
+ mAvrcpRemoteDevice.mRemoteFeatures = msg.arg1;
+ /* in case of AVRCP version < 1.3, no need to add track info */
+ if(mAvrcpRemoteDevice.isMetaDataSupported()) {
+ if(mRemoteNowPlayingList == null)
+ mRemoteNowPlayingList = new NowPlaying(mAvrcpRemoteDevice);
+ TrackInfo mTrack = new TrackInfo();
+ /* First element of NowPlayingList will be current Track
+ * for 1.3 this will be the only song
+ * for >= 1.4, others songs will have non-zero UID
+ */
+ mTrack.mItemUid = 0;
+ mRemoteNowPlayingList.addTrack(mTrack);
+ mRemoteNowPlayingList.setCurrTrack(mTrack);
+ }
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+ mAvrcpRemoteDevice.mAbsVolNotificationState =
+ AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
+ setAbsVolume(msg.arg1, msg.arg2);
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+ /* start BroadcastReceiver now */
+ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mAvrcpRemoteDevice.mNotificationLabel = msg.arg1;
+ mAvrcpRemoteDevice.mAbsVolNotificationState =
+ AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
+ registerReceiver(mBroadcastReceiver, filter);
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume);
+ Log.d(TAG," Sending Interim Response = "+ percentageVol + " label " + msg.arg1);
+ sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+ (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_INTERIM, percentageVol,
+ mAvrcpRemoteDevice.mNotificationLabel);
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_TRACK_CHANGED:
+ if(mRemoteNowPlayingList != null) {
+ mRemoteNowPlayingList.updateCurrentTrack((TrackInfo)msg.obj);
+
+ if (!mBroadcastMetadata) {
+ Log.d(TAG, "Metadata is not broadcasted, ignoring.");
+ return;
+ }
+
+ broadcastMetaDataChanged(AvrcpUtils.getMediaMetaData
+ (mRemoteNowPlayingList.getCurrentTrack()));
+ }
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ if(mRemoteMediaPlayers != null) {
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayTime = msg.arg2;
+
+ if (!mBroadcastMetadata) {
+ Log.d(TAG, "Metadata is not broadcasted, ignoring.");
+ return;
+ }
+
+ broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
+ (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
+ }
+ if(mRemoteNowPlayingList != null) {
+ mRemoteNowPlayingList.getCurrentTrack().mTrackLen = msg.arg1;
+ }
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+ if(mRemoteMediaPlayers != null) {
+ int status = msg.arg1;
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus = (byte) status;
+ if (status == AvrcpControllerConstants.PLAY_STATUS_PLAYING) {
+ a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), true);
+ } else if (status == AvrcpControllerConstants.PLAY_STATUS_PAUSED ||
+ status == AvrcpControllerConstants.PLAY_STATUS_STOPPED) {
+ a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), false);
+ }
+
+ if (mBroadcastMetadata) {
+ broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
+ (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
+ mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
+ } else {
+ Log.d(TAG, "Metadata is not broadcasted, ignoring.");
+ return;
+ }
+ }
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
+ if(mRemoteMediaPlayers != null)
+ mRemoteMediaPlayers.getAddressedPlayer().
+ setSupportedPlayerAppSetting((ByteBuffer)msg.obj);
+ break;
+ case AvrcpControllerConstants.MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
+ if(mRemoteMediaPlayers != null) {
+ mRemoteMediaPlayers.getAddressedPlayer().
+ updatePlayerAppSetting((ByteBuffer)msg.obj);
+ broadcastPlayerAppSettingChanged(getCurrentPlayerAppSetting());
+ }
break;
}
}
}
+ private void setAbsVolume(int absVol, int label)
+ {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ if (mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd) {
+ int newIndex = (maxVolume*absVol)/AvrcpControllerConstants.ABS_VOL_BASE;
+ Log.d(TAG," setAbsVolume ="+absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
+ " new = "+newIndex);
+ /*
+ * In some cases change in percentage is not sufficient enough to warrant
+ * change in index values which are in range of 0-15. For such cases
+ * no action is required
+ */
+ if (newIndex != currIndex) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+ AudioManager.FLAG_SHOW_UI);
+ }
+ }
+ else {
+ mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd = true;
+ absVol = (currIndex*AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume;
+ Log.d(TAG," SetAbsVol recvd for first time, respond with " + absVol);
+ }
+ sendAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), absVol, label);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_MUSIC) {
+ int streamValue = intent
+ .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ int streamPrevValue = intent.getIntExtra(
+ AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+ if (streamValue != -1 && streamValue != streamPrevValue) {
+ if ((mAvrcpRemoteDevice == null)
+ ||((mAvrcpRemoteDevice.mRemoteFeatures &
+ AvrcpControllerConstants.BTRC_FEAT_ABSOLUTE_VOLUME) == 0)
+ ||(mConnectedDevices.isEmpty()))
+ return;
+ if(mAvrcpRemoteDevice.mAbsVolNotificationState ==
+ AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP) {
+ int maxVol = mAudioManager.
+ getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.
+ getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex*
+ AvrcpControllerConstants.ABS_VOL_BASE)/maxVol);
+ Log.d(TAG," Abs Vol Notify Rsp Changed vol = "+ percentageVol);
+ sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
+ (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_CHANGED,
+ percentageVol, mAvrcpRemoteDevice.mNotificationLabel);
+ }
+ else if (mAvrcpRemoteDevice.mAbsVolNotificationState ==
+ AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP) {
+ Log.d(TAG," Don't Complete Notification Rsp. ");
+ mAvrcpRemoteDevice.mAbsVolNotificationState =
+ AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private void handlePassthroughRsp(int id, int keyState) {
+ Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState);
+ }
+
private void onConnectionStateChanged(boolean connected, byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
(Utils.getAddressStringFromByte(address));
- Log.d(TAG, "onConnectionStateChanged " + connected + " " + device);
- Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ Log.d(TAG, "onConnectionStateChanged " + connected + " " + device+ " size "+
+ mConnectedDevices.size());
+ if (device == null)
+ return;
int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
: BluetoothProfile.STATE_DISCONNECTED);
int newState = (connected ? BluetoothProfile.STATE_CONNECTED
: BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-// intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+ /* AVRCPControllerService supports single connection */
+ if(mConnectedDevices.size() > 0) {
+ Log.d(TAG,"A Connection already exists, returning");
+ return;
+ }
mConnectedDevices.add(device);
+ Message msg = mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+ oldState, device);
+ mHandler.sendMessage(msg);
} else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
mConnectedDevices.remove(device);
+ Message msg = mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+ oldState, device);
+ mHandler.sendMessage(msg);
}
}
- private void handlePassthroughRsp(int id, int keyState) {
- Log.d(TAG, "passthrough response received as: key: "
+ private void getRcFeatures(byte[] address, int features) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ Message msg = mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
+ mHandler.sendMessage(msg);
+ }
+ private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
+ /* TODO do we need to do anything here */
+ }
+ private void handleRegisterNotificationAbsVol(byte[] address, byte label)
+ {
+ Log.d(TAG,"handleRegisterNotificationAbsVol ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, label, 0);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handleSetAbsVolume(byte[] address, byte absVol, byte label)
+ {
+ Log.d(TAG,"handleSetAbsVolume ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ Message msg = mHandler.obtainMessage(
+ AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
+ mHandler.sendMessage(msg);
+ }
+
+ private void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
+ String[] attribVals)
+ {
+ Log.d(TAG,"onTrackChanged ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ TrackInfo mTrack = new TrackInfo(0, numAttributes, attributes, attribVals);
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_TRACK_CHANGED, numAttributes, 0, mTrack);
+ mHandler.sendMessage(msg);
+ }
+
+ private void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
+ Log.d(TAG,"onPlayPositionChanged ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
+ mHandler.sendMessage(msg);
+ }
+
+ private void onPlayStatusChanged(byte[] address, byte playStatus) {
+ if(DBG) Log.d(TAG,"onPlayStatusChanged " + playStatus);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playStatus, 0);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
+ Log.d(TAG,"handlePlayerAppSetting rspLen = " + rspLen);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING, 0, 0, bb);
+ mHandler.sendMessage(msg);
+ }
+
+ private void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
+ Log.d(TAG,"onPlayerAppSettingChanged ");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ if (!mConnectedDevices.contains(device))
+ return;
+ ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
+ Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
+ MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED, 0, 0, bb);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handleGroupNavigationRsp(int id, int keyState) {
+ Log.d(TAG, "group navigation response received as: key: "
+ id + " state: " + keyState);
}
@@ -273,4 +951,13 @@
private native void initNative();
private native void cleanupNative();
private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+ private native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+ int keyState);
+ private native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+ byte[] atttibIds, byte[]attribVal);
+ /* This api is used to send response to SET_ABS_VOL_CMD */
+ private native void sendAbsVolRspNative(byte[] address, int absVol, int label);
+ /* This api is used to inform remote for any volume level changes */
+ private native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+ int label);
}
diff --git a/src/com/android/bluetooth/avrcp/NowPlaying.java b/src/com/android/bluetooth/avrcp/NowPlaying.java
new file mode 100644
index 0000000..f78d7a6
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/NowPlaying.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ * TODO(sanketa): Rip out this feature as this is part of 1.6.
+ * @hide
+ */
+public class NowPlaying {
+ private static final boolean DBG = true;
+ private static final String TAG = "NowPlaying";
+
+ RemoteDevice mDevice;
+ private TrackInfo mCurrTrack;
+
+ private ArrayList<TrackInfo> mNowPlayingList;
+
+ public NowPlaying(RemoteDevice mRemoteDevice) {
+ mDevice = mRemoteDevice;
+ mNowPlayingList = new ArrayList<TrackInfo>();
+ mCurrTrack = null;
+ }
+
+ public void cleanup() {
+ mDevice = null;
+ if(mNowPlayingList != null) {
+ mNowPlayingList.clear();
+ }
+ mCurrTrack = null;
+ }
+
+ public RemoteDevice getDeviceRecords() {
+ return mDevice;
+ }
+
+ public void addTrack (TrackInfo mTrack) {
+ if(mNowPlayingList != null) {
+ mNowPlayingList.add(mTrack);
+ }
+ }
+
+ public void setCurrTrack (TrackInfo mTrack) {
+ mCurrTrack = mTrack;
+ }
+
+ public TrackInfo getCurrentTrack() {
+ return mCurrTrack;
+ }
+
+ public void updateCurrentTrack(TrackInfo mTrack) {
+ mCurrTrack.mAlbumTitle = mTrack.mAlbumTitle;
+ mCurrTrack.mArtistName = mTrack.mArtistName;
+ mCurrTrack.mGenre = mTrack.mGenre;
+ mCurrTrack.mTotalTracks = mTrack.mTotalTracks;
+ mCurrTrack.mTrackLen = mTrack.mTrackLen;
+ mCurrTrack.mTrackTitle = mTrack.mTrackTitle;
+ mCurrTrack.mTrackNum = mTrack.mTrackNum;
+ }
+
+ public TrackInfo getTrackFromId(int mTrackId) {
+ if(mTrackId == 0)
+ return getCurrentTrack();
+ else {
+ for(TrackInfo mTrackInfo: mNowPlayingList) {
+ if(mTrackInfo.mItemUid == mTrackId)
+ return mTrackInfo;
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java b/src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java
new file mode 100644
index 0000000..177742a
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/RemoteMediaPlayers.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ * TODO(sanketa): Rip out this feature as this is part of 1.6.
+ * @hide
+ */
+public class RemoteMediaPlayers {
+ private static final boolean DBG = true;
+ private static final String TAG = "RemoteMediaPlayers";
+
+ RemoteDevice mDevice;
+ private PlayerInfo mAddressedPlayer;
+ private PlayerInfo mBrowsedPlayer;
+ ArrayList<PlayerInfo> mMediaPlayerList;
+
+ public RemoteMediaPlayers (RemoteDevice mRemoteDevice) {
+ mDevice = mRemoteDevice;
+ mAddressedPlayer = null;
+ mBrowsedPlayer = null;
+ mMediaPlayerList = new ArrayList<PlayerInfo>();
+ }
+
+ public void cleanup() {
+ mDevice = null;
+ mAddressedPlayer = null;
+ mBrowsedPlayer = null;
+ if(mMediaPlayerList != null)
+ mMediaPlayerList.clear();
+ }
+ /*
+ * add a Player
+ */
+ public void addPlayer (PlayerInfo mPlayer) {
+ if(mMediaPlayerList != null)
+ mMediaPlayerList.add(mPlayer);
+ }
+ /*
+ * add players and Set AddressedPlayer and BrowsePlayer
+ */
+ public void setAddressedPlayer(PlayerInfo mPlayer) {
+ mAddressedPlayer = mPlayer;
+ }
+
+ public void setBrowsedPlayer(PlayerInfo mPlayer) {
+ mBrowsedPlayer = mPlayer;
+ }
+ /*
+ * Returns the currently addressed, browsed player
+ */
+ public PlayerInfo getAddressedPlayer() {
+ return mAddressedPlayer;
+ }
+
+ /*
+ * getPlayStatus of addressed player
+ */
+ public byte getPlayStatus() {
+ if(getAddressedPlayer() != null)
+ return getAddressedPlayer().mPlayStatus;
+ else
+ return AvrcpControllerConstants.PLAY_STATUS_STOPPED;
+ }
+
+ /*
+ * getPlayStatus of addressed player
+ */
+ public long getPlayPosition() {
+ if(getAddressedPlayer() != null)
+ return getAddressedPlayer().mPlayTime;
+ else
+ return AvrcpControllerConstants.PLAYING_TIME_INVALID;
+ }
+
+}
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index ac72282..29b5d5a 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -21,7 +21,6 @@
package com.android.bluetooth.btservice;
import android.app.AlarmManager;
-import android.app.Application;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
@@ -30,15 +29,15 @@
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothCallback;
-import android.bluetooth.IBluetoothManager;
-import android.bluetooth.IBluetoothManagerCallback;
import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.OobData;
+import android.bluetooth.UidTraffic;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -50,35 +49,40 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Base64;
import android.util.EventLog;
import android.util.Log;
-import android.util.Pair;
+import android.util.SparseArray;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.hid.HidService;
import com.android.bluetooth.hfp.HeadsetService;
-import com.android.bluetooth.hdp.HealthService;
-import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.pbapclient.PbapClientService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.internal.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
import java.io.FileDescriptor;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
-import java.util.Set;
import java.util.Map;
import java.util.Iterator;
-import java.util.Map.Entry;
import java.util.List;
-import android.content.pm.PackageManager;
import android.os.ServiceManager;
+import com.android.internal.app.IBatteryStats;
public class AdapterService extends Service {
private static final String TAG = "BluetoothAdapterService";
@@ -88,13 +92,16 @@
private static final int MIN_OFFLOADED_FILTERS = 10;
private static final int MIN_OFFLOADED_SCAN_STORAGE_BYTES = 1024;
//For Debugging only
- private static int sRefCount=0;
+ private static int sRefCount = 0;
+ private long mBluetoothStartTime = 0;
+ private final Object mEnergyInfoLock = new Object();
private int mStackReportedState;
private int mTxTimeTotalMs;
private int mRxTimeTotalMs;
private int mIdleTimeTotalMs;
private int mEnergyUsedTotalVoltAmpSecMicro;
+ private SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
private final ArrayList<ProfileService> mProfiles = new ArrayList<ProfileService>();
@@ -125,6 +132,13 @@
private static final int ADAPTER_SERVICE_TYPE=Service.START_STICKY;
+ private static final String[] DEVICE_TYPE_NAMES = new String[] {
+ "???",
+ "BR/EDR",
+ "LE",
+ "DUAL"
+ };
+
static {
classInitNative();
}
@@ -184,6 +198,7 @@
private AlarmManager mAlarmManager;
private PendingIntent mPendingAlarm;
+ private IBatteryStats mBatteryStats;
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock;
private String mWakeLockName;
@@ -198,6 +213,12 @@
debugLog("AdapterService() - REFCOUNT: CREATED. INSTANCE_COUNT" + sRefCount);
}
}
+
+ // This is initialized at the beginning in order to prevent
+ // NullPointerException from happening if AdapterService
+ // functions are called before BLE is turned on due to
+ // |mRemoteDevices| being null.
+ mRemoteDevices = new RemoteDevices(this);
}
public void onProfileConnectionStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) {
@@ -227,7 +248,10 @@
private void processInitProfilePriorities (BluetoothDevice device, ParcelUuid[] uuids){
HidService hidService = HidService.getHidService();
A2dpService a2dpService = A2dpService.getA2dpService();
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
HeadsetService headsetService = HeadsetService.getHeadsetService();
+ HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+ PbapClientService pbapClientService = PbapClientService.getPbapClientService();
// 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
@@ -238,7 +262,14 @@
hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
}
- // If we do not have a stored priority for A2DP then default to on.
+ // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
+ if ((headsetService != null) &&
+ ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) ||
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) &&
+ (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
+ headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
+ }
+
if ((a2dpService != null) &&
(BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink) ||
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) &&
@@ -246,21 +277,44 @@
a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
}
- if ((headsetService != null) &&
- ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) ||
- BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) &&
- (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))){
- headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
+ if ((headsetClientService != null) &&
+ ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) &&
+ (headsetClientService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
+ headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+
+ if ((a2dpSinkService != null) &&
+ (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource) &&
+ (a2dpSinkService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
+ a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+
+ if ((pbapClientService != null) &&
+ (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PBAP_PSE) &&
+ (pbapClientService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
+ pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
}
private void processProfileStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) {
- if (((profileId == BluetoothProfile.A2DP) ||(profileId == BluetoothProfile.HEADSET)) &&
+ // Profiles relevant to phones.
+ if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)) &&
(newState == BluetoothProfile.STATE_CONNECTED)){
debugLog( "Profile connected. Schedule missing profile connection if any");
connectOtherProfile(device, PROFILE_CONN_CONNECTED);
setProfileAutoConnectionPriority(device, profileId);
}
+
+ // Profiles relevant to Car Kitts.
+ if (((profileId == BluetoothProfile.A2DP_SINK) ||
+ (profileId == BluetoothProfile.HEADSET_CLIENT)) &&
+ (newState == BluetoothProfile.STATE_CONNECTED)) {
+ debugLog( "Profile connected. Schedule missing profile connection if any");
+ connectOtherProfile(device, PROFILE_CONN_CONNECTED);
+ setProfileAutoConnectionPriority(device, profileId);
+ }
+
IBluetooth.Stub binder = mBinder;
if (binder != null) {
try {
@@ -304,7 +358,7 @@
doUpdate=true;
}
}
- debugLog("onProfileServiceStateChange() serviceName=" + serviceName
+ debugLog("processProfileServiceStateChanged() serviceName=" + serviceName
+ ", state=" + state +", doUpdate=" + doUpdate);
if (!doUpdate) {
@@ -458,6 +512,8 @@
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
mSdpManager = SdpManager.init(this);
registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
@@ -494,7 +550,17 @@
for (int i=0; i < supportedProfileServices.length;i++) {
mProfileServicesState.put(supportedProfileServices[i].getName(),BluetoothAdapter.STATE_OFF);
}
- mRemoteDevices = new RemoteDevices(this);
+
+ // 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.cleanup();
mAdapterProperties.init(mRemoteDevices);
debugLog("BleOnProcessStart() - Make Bond State Machine");
@@ -502,6 +568,12 @@
mJniCallbacks.init(mBondStateMachine,mRemoteDevices);
+ try {
+ mBatteryStats.noteResetBleScan();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
//FIXME: Set static instance here???
setAdapterService(this);
@@ -583,7 +655,8 @@
// {@link #releaseWakeLock(String lockName)}, so a synchronization is needed here.
synchronized (this) {
if (mWakeLock != null) {
- mWakeLock.release();
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
mWakeLock = null;
}
}
@@ -667,8 +740,7 @@
for(int i=0; i<mUuids.length; i++) {
mUuids[i] = msg.getData().getParcelable("uuids" + i);
}
- processInitProfilePriorities((BluetoothDevice) msg.obj,
- mUuids);
+ processInitProfilePriorities((BluetoothDevice) msg.obj, mUuids);
}
break;
case MESSAGE_CONNECT_OTHER_PROFILES: {
@@ -999,14 +1071,25 @@
}
public boolean createBond(BluetoothDevice device, int transport) {
- if (!Utils.checkCaller()) {
+ if (!Utils.checkCallerAllowManagedProfiles(mService)) {
Log.w(TAG, "createBond() - Not allowed for non-active user");
return false;
}
AdapterService service = getService();
if (service == null) return false;
- return service.createBond(device, transport);
+ return service.createBond(device, transport, null);
+ }
+
+ public boolean createBondOutOfBand(BluetoothDevice device, int transport, OobData oobData) {
+ if (!Utils.checkCallerAllowManagedProfiles(mService)) {
+ Log.w(TAG, "createBondOutOfBand() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return false;
+ return service.createBond(device, transport, oobData);
}
public boolean cancelBondProcess(BluetoothDevice device) {
@@ -1339,13 +1422,14 @@
return service.reportActivityInfo();
}
- public void dump(ParcelFileDescriptor fd) {
- AdapterService service = getService();
- if (service == null) return;
- service.dump(fd.getFileDescriptor());
+ public void requestActivityInfo(ResultReceiver result) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY,
+ reportActivityInfo());
+ result.send(0, bundle);
}
- public void onLeServiceUp(){
+ public void onLeServiceUp(){
AdapterService service = getService();
if (service == null) return;
service.onLeServiceUp();
@@ -1356,6 +1440,13 @@
if (service == null) return;
service.onBrEdrDown();
}
+
+ public void dump(FileDescriptor fd, String[] args) {
+ PrintWriter writer = new PrintWriter(new FileOutputStream(fd));
+ AdapterService service = getService();
+ if (service == null) return;
+ service.dump(fd, writer, args);
+ }
};
// ----API Methods--------
@@ -1386,6 +1477,7 @@
mQuietmode = quietMode;
Message m = mAdapterStateMachine.obtainMessage(AdapterState.BLE_TURN_ON);
mAdapterStateMachine.sendMessage(m);
+ mBluetoothStartTime = System.currentTimeMillis();
return true;
}
@@ -1503,8 +1595,7 @@
}
}
-
- boolean createBond(BluetoothDevice device, int transport) {
+ boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH ADMIN permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
@@ -1519,6 +1610,12 @@
Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
msg.obj = device;
msg.arg1 = transport;
+
+ if (oobData != null) {
+ Bundle oobDataBundle = new Bundle();
+ oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
+ msg.setData(oobDataBundle);
+ }
mBondStateMachine.sendMessage(msg);
return true;
}
@@ -1535,8 +1632,14 @@
}
if (isQuietModeEnabled() == false) {
debugLog( "autoConnect() - Initiate auto connection on BT on...");
+ // Phone profiles.
autoConnectHeadset();
autoConnectA2dp();
+
+ // Car Kitt profiles.
+ autoConnectHeadsetClient();
+ autoConnectA2dpSink();
+ autoConnectPbapClient();
}
else {
debugLog( "autoConnect() - BT is in quiet mode. Not initiating auto connections");
@@ -1572,6 +1675,53 @@
}
}
+ private void autoConnectHeadsetClient() {
+ HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+ BluetoothDevice bondedDevices[] = getBondedDevices();
+ if ((bondedDevices == null) || (headsetClientService == null)) {
+ return;
+ }
+
+ for (BluetoothDevice device : bondedDevices) {
+ if (headsetClientService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT){
+ debugLog("autoConnectHeadsetClient() - Connecting Headset Client with " +
+ device.toString());
+ headsetClientService.connect(device);
+ }
+ }
+ }
+
+ private void autoConnectA2dpSink() {
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ BluetoothDevice bondedDevices[] = getBondedDevices();
+ if ((bondedDevices == null) || (a2dpSinkService == null)) {
+ return;
+ }
+
+ for (BluetoothDevice device : bondedDevices) {
+ if (a2dpSinkService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ debugLog("autoConnectA2dpSink() - Connecting A2DP Sink with " + device.toString());
+ a2dpSinkService.connect(device);
+ }
+ }
+ }
+
+ private void autoConnectPbapClient(){
+ PbapClientService pbapClientService = PbapClientService.getPbapClientService();
+ BluetoothDevice bondedDevices[] = getBondedDevices();
+ if ((bondedDevices == null) || (pbapClientService == null)) {
+ return;
+ }
+ for (BluetoothDevice device : bondedDevices) {
+ if (pbapClientService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ debugLog("autoConnectPbapClient() - Connecting PBAP Client with " +
+ device.toString());
+ pbapClientService.connect(device);
+ }
+ }
+ }
+
+
public void connectOtherProfile(BluetoothDevice device, int firstProfileStatus){
if ((mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES) == false) &&
(isQuietModeEnabled()== false)){
@@ -1615,7 +1765,7 @@
}
private void adjustOtherHeadsetPriorities(HeadsetService hsService,
- List<BluetoothDevice> connectedDeviceList) {
+ List<BluetoothDevice> connectedDeviceList) {
for (BluetoothDevice device : getBondedDevices()) {
if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
!connectedDeviceList.contains(device)) {
@@ -1624,8 +1774,8 @@
}
}
- private void adjustOtherSinkPriorities(A2dpService a2dpService,
- BluetoothDevice connectedDevice) {
+ private void adjustOtherSinkPriorities(A2dpService a2dpService,
+ BluetoothDevice connectedDevice) {
for (BluetoothDevice device : getBondedDevices()) {
if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
!device.equals(connectedDevice)) {
@@ -1634,23 +1784,88 @@
}
}
- void setProfileAutoConnectionPriority (BluetoothDevice device, int profileId){
- if (profileId == BluetoothProfile.HEADSET) {
- HeadsetService hsService = HeadsetService.getHeadsetService();
- List<BluetoothDevice> deviceList = hsService.getConnectedDevices();
- if ((hsService != null) &&
- (BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))){
- adjustOtherHeadsetPriorities(hsService, deviceList);
- hsService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
- }
- }
- else if (profileId == BluetoothProfile.A2DP) {
- A2dpService a2dpService = A2dpService.getA2dpService();
- if ((a2dpService != null) &&
- (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))){
- adjustOtherSinkPriorities(a2dpService, device);
- a2dpService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
- }
+ private void adjustOtherHeadsetClientPriorities(HeadsetClientService hsService,
+ BluetoothDevice connectedDevice) {
+ for (BluetoothDevice device : getBondedDevices()) {
+ if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
+ !device.equals(connectedDevice)) {
+ hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
+ private void adjustOtherA2dpSinkPriorities(A2dpSinkService a2dpService,
+ BluetoothDevice connectedDevice) {
+ for (BluetoothDevice device : getBondedDevices()) {
+ if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
+ !device.equals(connectedDevice)) {
+ a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
+ private void adjustOtherPbapClientPriorities(PbapClientService pbapService,
+ BluetoothDevice connectedDevice) {
+ for (BluetoothDevice device : getBondedDevices()) {
+ if (pbapService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
+ !device.equals(connectedDevice)) {
+ pbapService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ }
+ }
+
+ void setProfileAutoConnectionPriority (BluetoothDevice device, int profileId){
+ switch (profileId) {
+ case BluetoothProfile.HEADSET:
+ HeadsetService hsService = HeadsetService.getHeadsetService();
+ List<BluetoothDevice> deviceList = hsService.getConnectedDevices();
+ if ((hsService != null) &&
+ (BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))) {
+ adjustOtherHeadsetPriorities(hsService, deviceList);
+ hsService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+
+ case BluetoothProfile.A2DP:
+ A2dpService a2dpService = A2dpService.getA2dpService();
+ if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT !=
+ a2dpService.getPriority(device))) {
+ adjustOtherSinkPriorities(a2dpService, device);
+ a2dpService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if ((a2dpSinkService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT !=
+ a2dpSinkService.getPriority(device))) {
+ adjustOtherA2dpSinkPriorities(a2dpSinkService, device);
+ a2dpSinkService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+
+ case BluetoothProfile.HEADSET_CLIENT:
+ HeadsetClientService headsetClientService =
+ HeadsetClientService.getHeadsetClientService();
+ if ((headsetClientService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT !=
+ headsetClientService.getPriority(device))) {
+ adjustOtherHeadsetClientPriorities(headsetClientService, device);
+ headsetClientService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+
+ case BluetoothProfile.PBAP_CLIENT:
+ PbapClientService pbapClientService = PbapClientService.getPbapClientService();
+ if ((pbapClientService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT !=
+ pbapClientService.getPriority(device))) {
+ adjustOtherPbapClientPriorities(pbapClientService, device);
+ pbapClientService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ }
+ break;
+
+ default:
+ Log.w(TAG, "Attempting to set Auto Connect priority on invalid profile");
+ break;
}
}
@@ -1672,7 +1887,7 @@
return true;
}
- int getBondState(BluetoothDevice device) {
+ int getBondState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp == null) {
@@ -1689,6 +1904,7 @@
String getRemoteName(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (mRemoteDevices == null) return null;
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp == null) return null;
return deviceProp.getName();
@@ -1867,7 +2083,7 @@
ParcelUuid uuid, int port, int flag) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
int fd = connectSocketNative(Utils.getBytesFromAddress(device.getAddress()),
- type, Utils.uuidToByteArray(uuid), port, flag);
+ type, Utils.uuidToByteArray(uuid), port, flag, Binder.getCallingUid());
if (fd < 0) {
errorLog("Failed to connect socket");
return null;
@@ -1879,7 +2095,7 @@
ParcelUuid uuid, int port, int flag) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
int fd = createSocketChannelNative(type, serviceName,
- Utils.uuidToByteArray(uuid), port, flag);
+ Utils.uuidToByteArray(uuid), port, flag, Binder.getCallingUid());
if (fd < 0) {
errorLog("Failed to create socket channel");
return null;
@@ -1953,17 +2169,45 @@
private BluetoothActivityEnergyInfo reportActivityInfo() {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
- BluetoothActivityEnergyInfo info =
- new BluetoothActivityEnergyInfo(SystemClock.elapsedRealtime(), mStackReportedState,
- mTxTimeTotalMs, mRxTimeTotalMs, mIdleTimeTotalMs, mEnergyUsedTotalVoltAmpSecMicro);
- // Read on clear values; a record of data is created with
- // timstamp and new samples are collected until read again
- mStackReportedState = 0;
- mTxTimeTotalMs = 0;
- mRxTimeTotalMs = 0;
- mIdleTimeTotalMs = 0;
- mEnergyUsedTotalVoltAmpSecMicro = 0;
- return info;
+ synchronized (mEnergyInfoLock) {
+ final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(
+ SystemClock.elapsedRealtime(),
+ mStackReportedState,
+ mTxTimeTotalMs, mRxTimeTotalMs, mIdleTimeTotalMs,
+ mEnergyUsedTotalVoltAmpSecMicro);
+
+ // Count the number of entries that have byte counts > 0
+ int arrayLen = 0;
+ for (int i = 0; i < mUidTraffic.size(); i++) {
+ final UidTraffic traffic = mUidTraffic.valueAt(i);
+ if (traffic.getTxBytes() != 0 || traffic.getRxBytes() != 0) {
+ arrayLen++;
+ }
+ }
+
+ // Copy the traffic objects whose byte counts are > 0 and reset the originals.
+ final UidTraffic[] result = arrayLen > 0 ? new UidTraffic[arrayLen] : null;
+ int putIdx = 0;
+ for (int i = 0; i < mUidTraffic.size(); i++) {
+ final UidTraffic traffic = mUidTraffic.valueAt(i);
+ if (traffic.getTxBytes() != 0 || traffic.getRxBytes() != 0) {
+ result[putIdx++] = traffic.clone();
+ traffic.setRxBytes(0);
+ traffic.setTxBytes(0);
+ }
+ }
+
+ info.setUidTraffic(result);
+
+ // Read on clear values; a record of data is created with
+ // timstamp and new samples are collected until read again
+ mStackReportedState = 0;
+ mTxTimeTotalMs = 0;
+ mRxTimeTotalMs = 0;
+ mIdleTimeTotalMs = 0;
+ mEnergyUsedTotalVoltAmpSecMicro = 0;
+ return info;
+ }
}
public int getTotalNumOfTrackableAdvertisements() {
@@ -2034,21 +2278,15 @@
// extended to allow acquiring an arbitrary number of wake locks. The current interface
// takes |lockName| as a parameter in anticipation of that implementation.
private boolean acquireWakeLock(String lockName) {
- if (mWakeLock != null) {
- if (!lockName.equals(mWakeLockName)) {
- errorLog("Multiple wake lock acquisition attempted; aborting: " + lockName);
- return false;
+ synchronized (this) {
+ if (mWakeLock == null) {
+ mWakeLockName = lockName;
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
}
- // We're already holding the desired wake lock so return success.
- if (mWakeLock.isHeld()) {
- return true;
- }
+ if (!mWakeLock.isHeld())
+ mWakeLock.acquire();
}
-
- mWakeLockName = lockName;
- mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
- mWakeLock.acquire();
return true;
}
@@ -2063,36 +2301,49 @@
return false;
}
- mWakeLock.release();
- mWakeLock = null;
- mWakeLockName = null;
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
}
return true;
}
- private void energyInfoCallback (int status, int ctrl_state,
- long tx_time, long rx_time, long idle_time, long energy_used)
- throws RemoteException {
+ private void energyInfoCallback(int status, int ctrl_state, long tx_time, long rx_time,
+ long idle_time, long energy_used, UidTraffic[] data)
+ throws RemoteException {
if (ctrl_state >= BluetoothActivityEnergyInfo.BT_STACK_STATE_INVALID &&
ctrl_state <= BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_IDLE) {
- mStackReportedState = ctrl_state;
- mTxTimeTotalMs += tx_time;
- mRxTimeTotalMs += rx_time;
- mIdleTimeTotalMs += idle_time;
// Energy is product of mA, V and ms. If the chipset doesn't
// report it, we have to compute it from time
if (energy_used == 0) {
- energy_used = (long)((mTxTimeTotalMs * getTxCurrentMa()
- + mRxTimeTotalMs * getRxCurrentMa()
- + mIdleTimeTotalMs * getIdleCurrentMa()) * getOperatingVolt());
+ energy_used = (long)((tx_time * getTxCurrentMa()
+ + rx_time * getRxCurrentMa()
+ + idle_time * getIdleCurrentMa()) * getOperatingVolt());
}
- mEnergyUsedTotalVoltAmpSecMicro += energy_used;
+
+ synchronized (mEnergyInfoLock) {
+ mStackReportedState = ctrl_state;
+ mTxTimeTotalMs += tx_time;
+ mRxTimeTotalMs += rx_time;
+ mIdleTimeTotalMs += idle_time;
+ mEnergyUsedTotalVoltAmpSecMicro += energy_used;
+
+ for (UidTraffic traffic : data) {
+ UidTraffic existingTraffic = mUidTraffic.get(traffic.getUid());
+ if (existingTraffic == null) {
+ mUidTraffic.put(traffic.getUid(), traffic);
+ } else {
+ existingTraffic.addRxBytes(traffic.getRxBytes());
+ existingTraffic.addTxBytes(traffic.getTxBytes());
+ }
+ }
+ }
}
debugLog("energyInfoCallback() status = " + status +
- "tx_time = " + tx_time + "rx_time = " + rx_time +
- "idle_time = " + idle_time + "energy_used = " + energy_used +
- "ctrl_state = " + ctrl_state);
+ "tx_time = " + tx_time + "rx_time = " + rx_time +
+ "idle_time = " + idle_time + "energy_used = " + energy_used +
+ "ctrl_state = " + ctrl_state +
+ "traffic = " + Arrays.toString(data));
}
private int getIdleCurrentMa() {
@@ -2111,8 +2362,67 @@
return getResources().getInteger(R.integer.config_bluetooth_operating_voltage_mv) / 1000.0;
}
- private void dump(FileDescriptor fd) {
- // Collect profile information
+ private String getStateString() {
+ int state = getState();
+ switch (state) {
+ case BluetoothAdapter.STATE_OFF:
+ return "STATE_OFF";
+ case BluetoothAdapter.STATE_TURNING_ON:
+ return "STATE_TURNING_ON";
+ case BluetoothAdapter.STATE_ON:
+ return "STATE_ON";
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ return "STATE_TURNING_OFF";
+ case BluetoothAdapter.STATE_BLE_TURNING_ON:
+ return "STATE_BLE_TURNING_ON";
+ case BluetoothAdapter.STATE_BLE_ON:
+ return "STATE_BLE_ON";
+ case BluetoothAdapter.STATE_BLE_TURNING_OFF:
+ return "STATE_BLE_TURNING_OFF";
+ default:
+ return "UNKNOWN STATE: " + state;
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+ if (args.length > 0) {
+ debugLog("dumpsys arguments, check for protobuf output: " +
+ TextUtils.join(" ", args));
+ if (args[0].startsWith("--proto")) {
+ if (args[0].equals("--proto-java-bin")) {
+ dumpJava(fd);
+ } else {
+ dumpNative(fd, args);
+ }
+ return;
+ }
+ }
+
+ long onDuration = System.currentTimeMillis() - mBluetoothStartTime;
+ String onDurationString = String.format("%02d:%02d:%02d.%03d",
+ (int)(onDuration / (1000 * 60 * 60)),
+ (int)((onDuration / (1000 * 60)) % 60),
+ (int)((onDuration / 1000) % 60),
+ (int)(onDuration % 1000));
+
+ writer.println("Bluetooth Status");
+ writer.println(" enabled: " + isEnabled());
+ writer.println(" state: " + getStateString());
+ writer.println(" address: " + getAddress());
+ writer.println(" name: " + getName());
+ writer.println(" time since enabled: " + onDurationString + "\n");
+
+ writer.println("Bonded devices:");
+ for (BluetoothDevice device : getBondedDevices()) {
+ writer.println(" " + device.getAddress() +
+ " [" + DEVICE_TYPE_NAMES[device.getType()] + "] " +
+ device.getName());
+ }
+
+ // Dump profile information
StringBuilder sb = new StringBuilder();
synchronized (mProfiles) {
for (ProfileService profile : mProfiles) {
@@ -2120,25 +2430,28 @@
}
}
- // Dump Java based profiles first
- FileWriter fw = null;
- try {
- fw = new FileWriter(fd);
- fw.write(sb.toString());
- } catch (IOException ex) {
- errorLog("IOException writing profile status!");
- } finally {
- if (fw != null) {
- try {
- fw.close();
- } catch (IOException ex) {
- debugLog("IOException closing a file after writing the profile status");
- }
- }
+ writer.write(sb.toString());
+ writer.flush();
+
+ dumpNative(fd, args);
+ }
+
+ private void dumpJava(FileDescriptor fd) {
+ BluetoothProto.BluetoothLog log = new BluetoothProto.BluetoothLog();
+
+ for (ProfileService profile : mProfiles) {
+ profile.dumpProto(log);
}
- // Add native logs
- dumpNative(fd);
+ try {
+ FileOutputStream protoOut = new FileOutputStream(fd);
+ String protoOutString =
+ Base64.encodeToString(log.toByteArray(), Base64.DEFAULT);
+ protoOut.write(protoOutString.getBytes(StandardCharsets.UTF_8));
+ protoOut.close();
+ } catch (IOException e) {
+ errorLog("Unable to write Java protobuf to file descriptor.");
+ }
}
private void debugLog(String msg) {
@@ -2162,7 +2475,7 @@
private native static void classInitNative();
private native boolean initNative();
private native void cleanupNative();
- /*package*/ native boolean enableNative();
+ /*package*/ native boolean enableNative(boolean startRestricted);
/*package*/ native boolean disableNative();
/*package*/ native boolean setAdapterPropertyNative(int type, byte[] val);
/*package*/ native boolean getAdapterPropertiesNative();
@@ -2173,6 +2486,7 @@
/*package*/ native boolean getDevicePropertyNative(byte[] address, int type);
/*package*/ native boolean createBondNative(byte[] address, int transport);
+ /*package*/ native boolean createBondOutOfBandNative(byte[] address, int transport, OobData oobData);
/*package*/ native boolean removeBondNative(byte[] address);
/*package*/ native boolean cancelBondNative(byte[] address);
/*package*/ native boolean sdpSearchNative(byte[] address, byte[] uuid);
@@ -2192,15 +2506,15 @@
private native int readEnergyInfo();
// TODO(BT) move this to ../btsock dir
private native int connectSocketNative(byte[] address, int type,
- byte[] uuid, int port, int flag);
+ byte[] uuid, int port, int flag, int callingUid);
private native int createSocketChannelNative(int type, String serviceName,
- byte[] uuid, int port, int flag);
+ byte[] uuid, int port, int flag, int callingUid);
/*package*/ native boolean configHciSnoopLogNative(boolean enable);
/*package*/ native boolean factoryResetNative();
private native void alarmFiredNative();
- private native void dumpNative(FileDescriptor fd);
+ private native void dumpNative(FileDescriptor fd, String[] arguments);
private native void interopDatabaseClearNative();
private native void interopDatabaseAddNative(int feature, byte[] address, int length);
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 555e175..b47d324 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.Message;
+import android.os.UserManager;
import android.util.Log;
import com.android.internal.util.State;
@@ -360,7 +361,8 @@
removeMessages(BLE_START_TIMEOUT);
//Enable
- if (!adapterService.enableNative()) {
+ boolean isGuest = UserManager.get(mAdapterService).isGuestUser();
+ if (!adapterService.enableNative(isGuest)) {
errorLog("Error while turning Bluetooth on");
notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
transitionTo(mOffState);
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 7bed94a..353157e 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -23,6 +23,8 @@
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hid.HidService;
import com.android.bluetooth.hfp.HeadsetService;
+
+import android.bluetooth.OobData;
import android.content.Context;
import android.content.Intent;
import android.os.Message;
@@ -66,6 +68,8 @@
private PendingCommandState mPendingCommandState = new PendingCommandState();
private StableState mStableState = new StableState();
+ public static final String OOBDATA = "oobdata";
+
private BondStateMachine(AdapterService service,
AdapterProperties prop, RemoteDevices remoteDevices) {
super("BondStateMachine:");
@@ -110,7 +114,11 @@
switch(msg.what) {
case CREATE_BOND:
- createBond(dev, msg.arg1, true);
+ OobData oobData = null;
+ if (msg.getData() != null)
+ oobData = msg.getData().getParcelable(OOBDATA);
+
+ createBond(dev, msg.arg1, oobData, true);
break;
case REMOVE_BOND:
removeBond(dev, true);
@@ -171,7 +179,11 @@
switch (msg.what) {
case CREATE_BOND:
- result = createBond(dev, msg.arg1, false);
+ OobData oobData = null;
+ if (msg.getData() != null)
+ oobData = msg.getData().getParcelable(OOBDATA);
+
+ result = createBond(dev, msg.arg1, oobData, false);
break;
case REMOVE_BOND:
result = removeBond(dev, false);
@@ -204,15 +216,7 @@
mAdapterService.setSimAccessPermission(dev,
BluetoothDevice.ACCESS_UNKNOWN);
// Set the profile Priorities to undefined
- clearProfilePriorty(dev);
- }
- else if (newState == BluetoothDevice.BOND_BONDED)
- {
- // Do not set profile priority
- // Profile priority should be set after SDP completion
-
- // Restore the profile priorty settings
- //setProfilePriorty(dev);
+ clearProfilePriority(dev);
}
}
else if(!mDevices.contains(dev))
@@ -288,11 +292,19 @@
return false;
}
- private boolean createBond(BluetoothDevice dev, int transport, boolean transition) {
+ private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
+ boolean transition) {
if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
infoLog("Bond address is:" + dev);
byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
- if (!mAdapterService.createBondNative(addr, transport)) {
+ boolean result;
+ if (oobData != null) {
+ result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
+ } else {
+ result = mAdapterService.createBondNative(addr, transport);
+ }
+
+ if (!result) {
sendIntent(dev, BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOVED);
return false;
@@ -430,28 +442,7 @@
sendMessage(msg);
}
- private void setProfilePriorty (BluetoothDevice device){
- HidService hidService = HidService.getHidService();
- A2dpService a2dpService = A2dpService.getA2dpService();
- HeadsetService headsetService = HeadsetService.getHeadsetService();
-
- if ((hidService != null) &&
- (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
- hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
- }
-
- if ((a2dpService != null) &&
- (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
- a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
- }
-
- if ((headsetService != null) &&
- (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
- headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
- }
- }
-
- private void clearProfilePriorty (BluetoothDevice device){
+ private void clearProfilePriority(BluetoothDevice device) {
HidService hidService = HidService.getHidService();
A2dpService a2dpService = A2dpService.getA2dpService();
HeadsetService headsetService = HeadsetService.getHeadsetService();
@@ -462,6 +453,10 @@
a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
if(headsetService != null)
headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
+
+ // Clear Absolute Volume black list
+ if(a2dpService != null)
+ a2dpService.resetAvrcpBlacklist(device);
}
private void infoLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 0fc4718..93da040 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -30,7 +30,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.a2dp.A2dpSinkService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.avrcp.AvrcpControllerService;
import com.android.bluetooth.hdp.HealthService;
import com.android.bluetooth.hfp.HeadsetService;
@@ -40,6 +40,7 @@
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.map.BluetoothMapService;
import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
public class Config {
private static final String TAG = "AdapterServiceConfig";
@@ -60,7 +61,8 @@
BluetoothMapService.class,
HeadsetClientService.class,
AvrcpControllerService.class,
- SapService.class
+ SapService.class,
+ PbapClientService.class
};
/**
* Resource flag to indicate whether profile is supported or not.
@@ -77,6 +79,7 @@
R.bool.profile_supported_hfpclient,
R.bool.profile_supported_avrcp_controller,
R.bool.profile_supported_sap,
+ R.bool.profile_supported_pbapclient
};
private static Class[] SUPPORTED_PROFILES = new Class[0];
@@ -132,6 +135,8 @@
profileIndex = BluetoothProfile.AVRCP_CONTROLLER;
} else if (profile == SapService.class) {
profileIndex = BluetoothProfile.SAP;
+ } else if (profile == PbapClientService.class) {
+ profileIndex = BluetoothProfile.PBAP_CLIENT;
}
if (profileIndex == -1) {
diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java
index 7a6908f..0202947 100644
--- a/src/com/android/bluetooth/btservice/ProfileService.java
+++ b/src/com/android/bluetooth/btservice/ProfileService.java
@@ -164,6 +164,10 @@
sb.append("\nProfile: " + mName + "\n");
}
+ public void dumpProto(BluetoothProto.BluetoothLog proto) {
+ // Do nothing
+ }
+
// with indenting for subclasses
public static void println(StringBuilder sb, String s) {
sb.append(" ");
diff --git a/src/com/android/bluetooth/btservice/bluetooth.proto b/src/com/android/bluetooth/btservice/bluetooth.proto
new file mode 100644
index 0000000..11311ea
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/bluetooth.proto
@@ -0,0 +1,202 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+// Author: pkanwar@google.com (Pankaj Kanwar)
+// Protos for uploading bluetooth metrics.
+
+syntax = "proto2";
+
+package com.android.bluetooth.btservice;
+
+option java_package = "com.android.bluetooth.btservice";
+option java_outer_classname = "BluetoothProto";
+//option (datapol.file_vetting_status) = "latest";
+
+// import "storage/datapol/annotations/proto/semantic_annotations.proto";
+
+message BluetoothLog {
+
+ // Session information that gets logged for every BT connection.
+ repeated BluetoothSession session = 1;
+
+ // Session information that gets logged for every Pair event.
+ repeated PairEvent pair_event = 2;
+
+ // Information for Wake locks.
+ repeated WakeEvent wake_event = 3;
+
+ // Scan event information.
+ repeated ScanEvent scan_event = 4;
+}
+
+// The information about the device.
+message DeviceInfo {
+
+ // Device type.
+ enum DeviceType {
+
+ // Type is unknown.
+ DEVICE_TYPE_UNKNOWN = 0;
+
+ DEVICE_TYPE_BREDR = 1;
+
+ DEVICE_TYPE_LE = 2;
+
+ DEVICE_TYPE_DUMO = 3;
+ }
+
+ // Device class
+ // https://cs.corp.google.com/#android/system/bt/stack/include/btm_api.h&q=major_computer.
+ optional int32 device_class = 1;
+
+ // Device type.
+ optional DeviceType device_type = 2;
+}
+
+// Information that gets logged for every Bluetooth connection.
+message BluetoothSession {
+
+ // Type of technology used in the connection.
+ enum ConnectionTechnologyType {
+
+ CONNECTION_TECHNOLOGY_TYPE_UNKNOWN = 0;
+
+ CONNECTION_TECHNOLOGY_TYPE_LE = 1;
+
+ CONNECTION_TECHNOLOGY_TYPE_BREDR = 2;
+ }
+
+ // Duration of the session.
+ optional int64 session_duration_sec = 2;
+
+ // Technology type.
+ optional ConnectionTechnologyType connection_technology_type = 3;
+
+ // Reason for disconnecting.
+ optional string disconnect_reason = 4;
+
+ // The information about the device which it is connected to.
+ optional DeviceInfo device_connected_to = 5;
+
+ // The information about the RFComm session.
+ optional RFCommSession rfcomm_session = 6;
+
+ // The information about the A2DP session.
+ optional A2DPSession a2dp_session = 7;
+}
+
+message RFCommSession {
+
+ // bytes transmitted.
+ optional int32 rx_bytes = 1;
+
+ // bytes transmitted.
+ optional int32 tx_bytes = 2;
+}
+
+// Session information that gets logged for every A2DP session.
+message A2DPSession {
+
+ // Media timer in milliseconds.
+ optional int32 media_timer_min_millis = 1;
+
+ // Media timer in milliseconds.
+ optional int32 media_timer_max_millis = 2;
+
+ // Media timer in milliseconds.
+ optional int32 media_timer_avg_millis = 3;
+
+ // Buffer overruns count.
+ optional int32 buffer_overruns_max_count = 4;
+
+ // Buffer overruns total.
+ optional int32 buffer_overruns_total = 5;
+
+ // Buffer underruns average.
+ optional float buffer_underruns_average = 6;
+
+ // Buffer underruns count.
+ optional int32 buffer_underruns_count = 7;
+}
+
+message PairEvent {
+
+ // The reason for disconnecting
+ // https://cs.corp.google.com/#android/system/bt/stack/include/hcidefs.h&q=failed_establish.
+ optional int32 disconnect_reason = 1;
+
+ // Pair event time
+ optional int64 event_time_millis = 2; // [(datapol.semantic_type) = ST_TIMESTAMP];
+
+ // The information about the device which it is paired to.
+ optional DeviceInfo device_paired_with = 3;
+}
+
+message WakeEvent {
+
+ // Information about the wake event type.
+ enum WakeEventType {
+
+ // Type is unknown.
+ UNKNOWN = 0;
+
+ // WakeLock was acquired.
+ ACQUIRED = 1;
+
+ // WakeLock was released.
+ RELEASED = 2;
+ }
+
+ // Information about the wake event type.
+ optional WakeEventType wake_event_type = 1;
+
+ // Initiator of the scan. Only the first three names will be stored.
+ // e.g. com.google.gms.
+ optional string requestor = 2;
+
+ // Name of the wakelock (e.g. bluedroid_timer).
+ optional string name = 3;
+
+ // Time of the event.
+ optional int64 event_time_millis = 4; // [(datapol.semantic_type) = ST_TIMESTAMP];
+}
+
+message ScanEvent {
+
+ // Scan type.
+ enum ScanTechnologyType {
+
+ // Scan Type is unknown.
+ SCAN_TYPE_UNKNOWN = 0;
+
+ SCAN_TECH_TYPE_LE = 1;
+
+ SCAN_TECH_TYPE_BREDR = 2;
+
+ SCAN_TECH_TYPE_BOTH = 3;
+ }
+
+ // Scan event type.
+ enum ScanEventType {
+
+ // Scan started.
+ SCAN_EVENT_START = 0;
+
+ // Scan stopped.
+ SCAN_EVENT_STOP = 1;
+ }
+
+ // Scan event type.
+ optional ScanEventType scan_event_type = 1;
+
+ // Initiator of the scan. Only the first three names will be stored.
+ // e.g. com.google.gms.
+ optional string initiator = 2;
+
+ // Technology used for scanning.
+ optional ScanTechnologyType scan_technology_type = 3;
+
+ // Number of results returned.
+ optional int32 number_results = 4;
+
+ // Time of the event.
+ optional int64 event_time_millis = 5; // [(datapol.semantic_type) = ST_TIMESTAMP];
+}
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
new file mode 100644
index 0000000..e73f415
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.gatt;
+
+import android.bluetooth.le.ScanSettings;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import com.android.bluetooth.btservice.BluetoothProto;
+/**
+ * ScanStats class helps keep track of information about scans
+ * on a per application basis.
+ * @hide
+ */
+/*package*/ class AppScanStats {
+ static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+
+ /* ContextMap here is needed to grab Apps and Connections */
+ ContextMap contextMap;
+
+ /* GattService is needed to add scan event protos to be dumped later */
+ GattService gattService;
+
+ class LastScan {
+ long duration;
+ long timestamp;
+ boolean opportunistic;
+ boolean timeout;
+ boolean background;
+ int results;
+
+ public LastScan(long timestamp, long duration,
+ boolean opportunistic, boolean background) {
+ this.duration = duration;
+ this.timestamp = timestamp;
+ this.opportunistic = opportunistic;
+ this.background = background;
+ this.results = 0;
+ }
+ }
+
+ static final int NUM_SCAN_DURATIONS_KEPT = 5;
+
+ // This constant defines the time window an app can scan multiple times.
+ // Any single app can scan up to |NUM_SCAN_DURATIONS_KEPT| times during
+ // this window. Once they reach this limit, they must wait until their
+ // earliest recorded scan exits this window.
+ static final long EXCESSIVE_SCANNING_PERIOD_MS = 30 * 1000;
+
+ String appName;
+ int scansStarted = 0;
+ int scansStopped = 0;
+ boolean isScanning = false;
+ boolean isRegistered = false;
+ long minScanTime = Long.MAX_VALUE;
+ long maxScanTime = 0;
+ long totalScanTime = 0;
+ List<LastScan> lastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT + 1);
+ long startTime = 0;
+ long stopTime = 0;
+ int results = 0;
+
+ public AppScanStats(String name, ContextMap map, GattService service) {
+ appName = name;
+ contextMap = map;
+ gattService = service;
+ }
+
+ synchronized void addResult() {
+ if (!lastScans.isEmpty())
+ lastScans.get(lastScans.size() - 1).results++;
+
+ results++;
+ }
+
+ synchronized void recordScanStart(ScanSettings settings) {
+ if (isScanning)
+ return;
+
+ this.scansStarted++;
+ isScanning = true;
+ startTime = System.currentTimeMillis();
+
+ LastScan scan = new LastScan(startTime, 0, false, false);
+ if (settings != null) {
+ scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
+ scan.background = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+ }
+ lastScans.add(scan);
+
+ BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
+ scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START);
+ scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
+ scanEvent.setEventTimeMillis(System.currentTimeMillis());
+ scanEvent.setInitiator(truncateAppName(appName));
+ gattService.addScanEvent(scanEvent);
+ }
+
+ synchronized void recordScanStop() {
+ if (!isScanning)
+ return;
+
+ this.scansStopped++;
+ isScanning = false;
+ stopTime = System.currentTimeMillis();
+ long scanDuration = stopTime - startTime;
+
+ minScanTime = Math.min(scanDuration, minScanTime);
+ maxScanTime = Math.max(scanDuration, maxScanTime);
+ totalScanTime += scanDuration;
+
+ LastScan curr = lastScans.get(lastScans.size() - 1);
+ curr.duration = scanDuration;
+
+ if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) {
+ lastScans.remove(0);
+ }
+
+ BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
+ scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP);
+ scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
+ scanEvent.setEventTimeMillis(System.currentTimeMillis());
+ scanEvent.setInitiator(truncateAppName(appName));
+ gattService.addScanEvent(scanEvent);
+ }
+
+ synchronized void setScanTimeout() {
+ if (!isScanning)
+ return;
+
+ if (!lastScans.isEmpty()) {
+ LastScan curr = lastScans.get(lastScans.size() - 1);
+ curr.timeout = true;
+ }
+ }
+
+ synchronized boolean isScanningTooFrequently() {
+ if (lastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
+ return false;
+ }
+
+ return (System.currentTimeMillis() - lastScans.get(0).timestamp) <
+ EXCESSIVE_SCANNING_PERIOD_MS;
+ }
+
+ // This function truncates the app name for privacy reasons. Apps with
+ // four part package names or more get truncated to three parts, and apps
+ // with three part package names names get truncated to two. Apps with two
+ // or less package names names are untouched.
+ // Examples: one.two.three.four => one.two.three
+ // one.two.three => one.two
+ private String truncateAppName(String name) {
+ String initiator = name;
+ String[] nameSplit = initiator.split("\\.");
+ if (nameSplit.length > 3) {
+ initiator = nameSplit[0] + "." +
+ nameSplit[1] + "." +
+ nameSplit[2];
+ } else if (nameSplit.length == 3) {
+ initiator = nameSplit[0] + "." + nameSplit[1];
+ }
+
+ return initiator;
+ }
+
+ synchronized void dumpToString(StringBuilder sb) {
+ long currTime = System.currentTimeMillis();
+ long maxScan = maxScanTime;
+ long minScan = minScanTime;
+ long scanDuration = 0;
+
+ if (lastScans.isEmpty())
+ return;
+
+ if (isScanning) {
+ scanDuration = currTime - startTime;
+ minScan = Math.min(scanDuration, minScan);
+ maxScan = Math.max(scanDuration, maxScan);
+ }
+
+ if (minScan == Long.MAX_VALUE) {
+ minScan = 0;
+ }
+
+ long avgScan = 0;
+ if (scansStarted > 0) {
+ avgScan = (totalScanTime + scanDuration) / scansStarted;
+ }
+
+ LastScan lastScan = lastScans.get(lastScans.size() - 1);
+ sb.append(" " + appName);
+ if (isRegistered) sb.append(" (Registered)");
+ if (lastScan.opportunistic) sb.append(" (Opportunistic)");
+ if (lastScan.background) sb.append(" (Background)");
+ if (lastScan.timeout) sb.append(" (Forced-Opportunistic)");
+ sb.append("\n");
+
+ sb.append(" LE scans (started/stopped) : " +
+ scansStarted + " / " +
+ scansStopped + "\n");
+ sb.append(" Scan time in ms (min/max/avg/total): " +
+ minScan + " / " +
+ maxScan + " / " +
+ avgScan + " / " +
+ totalScanTime + "\n");
+ sb.append(" Total number of results : " +
+ results + "\n");
+
+ if (lastScans.size() != 0) {
+ int lastScansSize = scansStopped < NUM_SCAN_DURATIONS_KEPT ?
+ scansStopped : NUM_SCAN_DURATIONS_KEPT;
+ sb.append(" Last " + lastScansSize +
+ " scans :\n");
+
+ for (int i = 0; i < lastScansSize; i++) {
+ LastScan scan = lastScans.get(i);
+ Date timestamp = new Date(scan.timestamp);
+ sb.append(" " + dateFormat.format(timestamp) + " - ");
+ sb.append(scan.duration + "ms ");
+ if (scan.opportunistic) sb.append("Opp ");
+ if (scan.background) sb.append("Back ");
+ if (scan.timeout) sb.append("Forced ");
+ sb.append(scan.results + " results");
+ sb.append("\n");
+ }
+ }
+
+ ContextMap.App appEntry = contextMap.getByName(appName);
+ if (appEntry != null && isRegistered) {
+ sb.append(" Application ID : " +
+ appEntry.id + "\n");
+ sb.append(" UUID : " +
+ appEntry.uuid + "\n");
+
+ if (isScanning) {
+ sb.append(" Current scan duration in ms : " +
+ scanDuration + "\n");
+ }
+
+ List<ContextMap.Connection> connections =
+ contextMap.getConnectionByApp(appEntry.id);
+
+ sb.append(" Connections: " + connections.size() + "\n");
+
+ Iterator<ContextMap.Connection> ii = connections.iterator();
+ while(ii.hasNext()) {
+ ContextMap.Connection connection = ii.next();
+ long connectionTime = System.currentTimeMillis() - connection.startTime;
+ sb.append(" " + connection.connId + ": " +
+ connection.address + " " + connectionTime + "ms\n");
+ }
+ }
+ sb.append("\n");
+ }
+}
diff --git a/src/com/android/bluetooth/gatt/CallbackInfo.java b/src/com/android/bluetooth/gatt/CallbackInfo.java
index 25dfd4c..db70a53 100644
--- a/src/com/android/bluetooth/gatt/CallbackInfo.java
+++ b/src/com/android/bluetooth/gatt/CallbackInfo.java
@@ -27,21 +27,12 @@
class CallbackInfo {
String address;
int status;
- int srvcType;
- int srvcInstId;
- UUID srvcUuid;
- int charInstId;
- UUID charUuid;
+ int handle;
- CallbackInfo(String address, int status, int srvcType, int srvcInstId,
- UUID srvcUuid, int charInstId, UUID charUuid) {
+ CallbackInfo(String address, int status, int handle) {
this.address = address;
this.status = status;
- this.srvcType = srvcType;
- this.srvcInstId = srvcInstId;
- this.srvcUuid = srvcUuid;
- this.charInstId = charInstId;
- this.charUuid = charUuid;
+ this.handle = handle;
}
CallbackInfo(String address, int status) {
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index f492237..e3044d5 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -15,6 +15,8 @@
*/
package com.android.bluetooth.gatt;
+import android.content.Context;
+import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
@@ -30,6 +32,7 @@
import java.util.HashMap;
import java.util.Map;
+import com.android.bluetooth.btservice.BluetoothProto;
/**
* Helper class that keeps track of registered GATT applications.
* This class manages application callbacks and keeps track of GATT connections.
@@ -45,11 +48,13 @@
int connId;
String address;
int appId;
+ long startTime;
Connection(int connId, String address,int appId) {
this.connId = connId;
this.address = address;
this.appId = appId;
+ this.startTime = System.currentTimeMillis();
}
}
@@ -63,6 +68,12 @@
/** The id of the application */
int id;
+ /** The package name of the application */
+ String name;
+
+ /** Statistics for this app */
+ AppScanStats appScanStats;
+
/** Application callbacks */
T callback;
@@ -78,9 +89,11 @@
/**
* Creates a new app context.
*/
- App(UUID uuid, T callback) {
+ App(UUID uuid, T callback, String name, AppScanStats appScanStats) {
this.uuid = uuid;
this.callback = callback;
+ this.name = name;
+ this.appScanStats = appScanStats;
}
/**
@@ -123,15 +136,30 @@
/** Our internal application list */
List<App> mApps = new ArrayList<App>();
+ /** Internal map to keep track of logging information by app name */
+ HashMap<String, AppScanStats> mAppScanStats = new HashMap<String, AppScanStats>();
+
/** Internal list of connected devices **/
Set<Connection> mConnections = new HashSet<Connection>();
/**
* Add an entry to the application context list.
*/
- void add(UUID uuid, T callback) {
+ void add(UUID uuid, T callback, GattService service) {
+ String appName = service.getPackageManager().getNameForUid(
+ Binder.getCallingUid());
+ if (appName == null) {
+ // Assign an app name if one isn't found
+ appName = "Unknown App (UID: " + Binder.getCallingUid() + ")";
+ }
synchronized (mApps) {
- mApps.add(new App(uuid, callback));
+ AppScanStats appScanStats = mAppScanStats.get(appName);
+ if (appScanStats == null) {
+ appScanStats = new AppScanStats(appName, this, service);
+ mAppScanStats.put(appName, appScanStats);
+ }
+ mApps.add(new App(uuid, callback, appName, appScanStats));
+ appScanStats.isRegistered = true;
}
}
@@ -141,10 +169,11 @@
void remove(UUID uuid) {
synchronized (mApps) {
Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
App entry = i.next();
if (entry.uuid.equals(uuid)) {
entry.unlinkToDeath();
+ entry.appScanStats.isRegistered = false;
i.remove();
break;
}
@@ -158,10 +187,11 @@
void remove(int id) {
synchronized (mApps) {
Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
App entry = i.next();
if (entry.id == id) {
entry.unlinkToDeath();
+ entry.appScanStats.isRegistered = false;
i.remove();
break;
}
@@ -187,7 +217,7 @@
void removeConnection(int id, int connId) {
synchronized (mConnections) {
Iterator<Connection> i = mConnections.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Connection connection = i.next();
if (connection.connId == connId) {
i.remove();
@@ -202,7 +232,7 @@
*/
App getById(int id) {
Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
App entry = i.next();
if (entry.id == id) return entry;
}
@@ -215,7 +245,7 @@
*/
App getByUuid(UUID uuid) {
Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
App entry = i.next();
if (entry.uuid.equals(uuid)) return entry;
}
@@ -224,12 +254,43 @@
}
/**
+ * Get an application context by the calling Apps name.
+ */
+ App getByName(String name) {
+ Iterator<App> i = mApps.iterator();
+ while (i.hasNext()) {
+ App entry = i.next();
+ if (entry.name.equals(name)) return entry;
+ }
+ Log.e(TAG, "Context not found for name " + name);
+ return null;
+ }
+
+ /**
+ * Get Logging info by ID
+ */
+ AppScanStats getAppScanStatsById(int id) {
+ App temp = getById(id);
+ if (temp != null) {
+ return temp.appScanStats;
+ }
+ return null;
+ }
+
+ /**
+ * Get Logging info by application name
+ */
+ AppScanStats getAppScanStatsByName(String name) {
+ return mAppScanStats.get(name);
+ }
+
+ /**
* Get the device addresses for all connected devices
*/
Set<String> getConnectedDevices() {
Set<String> addresses = new HashSet<String>();
Iterator<Connection> i = mConnections.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Connection connection = i.next();
addresses.add(connection.address);
}
@@ -241,7 +302,7 @@
*/
App getByConnId(int connId) {
Iterator<Connection> ii = mConnections.iterator();
- while(ii.hasNext()) {
+ while (ii.hasNext()) {
Connection connection = ii.next();
if (connection.connId == connId){
return getById(connection.appId);
@@ -258,7 +319,7 @@
if (entry == null) return null;
Iterator<Connection> i = mConnections.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Connection connection = i.next();
if (connection.address.equals(address) && connection.appId == id)
return connection.connId;
@@ -271,7 +332,7 @@
*/
String addressByConnId(int connId) {
Iterator<Connection> i = mConnections.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Connection connection = i.next();
if (connection.connId == connId) return connection.address;
}
@@ -281,7 +342,7 @@
List<Connection> getConnectionByApp(int appId) {
List<Connection> currentConnections = new ArrayList<Connection>();
Iterator<Connection> i = mConnections.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Connection connection = i.next();
if (connection.appId == appId)
currentConnections.add(connection);
@@ -295,9 +356,10 @@
void clear() {
synchronized (mApps) {
Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
App entry = i.next();
entry.unlinkToDeath();
+ entry.appScanStats.isRegistered = false;
i.remove();
}
}
@@ -322,22 +384,15 @@
* Logs debug information.
*/
void dump(StringBuilder sb) {
- sb.append(" Entries: " + mApps.size() + "\n");
+ sb.append(" Entries: " + mAppScanStats.size() + "\n\n");
- Iterator<App> i = mApps.iterator();
- while(i.hasNext()) {
- App entry = i.next();
- List<Connection> connections = getConnectionByApp(entry.id);
+ Iterator<Map.Entry<String, AppScanStats>> it = mAppScanStats.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, AppScanStats> entry = it.next();
- sb.append("\n Application Id: " + entry.id + "\n");
- sb.append(" UUID: " + entry.uuid + "\n");
- sb.append(" Connections: " + connections.size() + "\n");
-
- Iterator<Connection> ii = connections.iterator();
- while(ii.hasNext()) {
- Connection connection = ii.next();
- sb.append(" " + connection.connId + ": " + connection.address + "\n");
- }
+ String name = entry.getKey();
+ AppScanStats appScanStats = entry.getValue();
+ appScanStats.dumpToString(sb);
}
}
}
diff --git a/src/com/android/bluetooth/gatt/GattDbElement.java b/src/com/android/bluetooth/gatt/GattDbElement.java
new file mode 100644
index 0000000..6be1dc6
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/GattDbElement.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Helper class for passing gatt db elements between java and JNI, equal to
+ * native btgatt_db_element_t.
+ * @hide
+ */
+public class GattDbElement {
+
+ public static final int TYPE_PRIMARY_SERVICE = 0;
+ public static final int TYPE_SECONDARY_SERVICE = 1;
+ public static final int TYPE_INCLUDED_SERVICE = 2;
+ public static final int TYPE_CHARACTERISTIC = 3;
+ public static final int TYPE_DESCRIPTOR = 4;
+
+ public GattDbElement() {}
+
+ public int id;
+ public UUID uuid;
+ public int type;
+ public int attributeHandle;
+
+ /*
+ * If type is TYPE_PRIMARY_SERVICE, or TYPE_SECONDARY_SERVICE,
+ * this contains the start and end attribute handles.
+ */
+ public int startHandle;
+ public int endHandle;
+
+ /*
+ * If type is TYPE_CHARACTERISTIC, this contains the properties of
+ * the characteristic.
+ */
+ public int properties;
+}
\ No newline at end of file
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 4847c53..dfb08d3 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -21,6 +21,10 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattIncludedService;
+import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothGattCallback;
@@ -39,13 +43,14 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserHandle;
+import android.os.WorkSource;
import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.BluetoothProto;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -61,6 +66,7 @@
import java.util.UUID;
import java.util.concurrent.TimeUnit;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
/**
* Provides Bluetooth Gatt profile, as a service in
* the Bluetooth application.
@@ -90,10 +96,9 @@
UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB")
};
- /**
- * Search queue to serialize remote onbject inspection.
- */
- SearchQueue mSearchQueue = new SearchQueue();
+ private static final UUID[] FIDO_UUIDS = {
+ UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
+ };
/**
* List of our registered clients.
@@ -121,6 +126,16 @@
*/
private List<ServiceDeclaration> mServiceDeclarations = new ArrayList<ServiceDeclaration>();
+ private Map<Integer, List<BluetoothGattService>> gattClientDatabases =
+ new HashMap<Integer, List<BluetoothGattService>>();
+
+ static final int NUM_SCAN_EVENTS_KEPT = 20;
+ /**
+ * Internal list of scan events to use with the proto
+ */
+ ArrayList<BluetoothProto.ScanEvent> mScanEvents =
+ new ArrayList<BluetoothProto.ScanEvent>(NUM_SCAN_EVENTS_KEPT);
+
private ServiceDeclaration addDeclaration() {
synchronized (mServiceDeclarations) {
mServiceDeclarations.add(new ServiceDeclaration());
@@ -189,7 +204,6 @@
if (DBG) Log.d(TAG, "stop()");
mClientMap.clear();
mServerMap.clear();
- mSearchQueue.clear();
mHandleMap.clear();
mServiceDeclarations.clear();
mReliableQueue.clear();
@@ -206,6 +220,36 @@
return true;
}
+ boolean permissionCheck(int connId, int handle) {
+ List<BluetoothGattService> db = gattClientDatabases.get(connId);
+
+ for (BluetoothGattService service : db) {
+ for (BluetoothGattCharacteristic characteristic: service.getCharacteristics()) {
+ if (handle == characteristic.getInstanceId()) {
+ if ((isRestrictedCharUuid(characteristic.getUuid()) ||
+ isRestrictedSrvcUuid(service.getUuid())) &&
+ (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)))
+ return false;
+ else
+ return true;
+ }
+
+ for (BluetoothGattDescriptor descriptor: characteristic.getDescriptors()) {
+ if (handle == descriptor.getInstanceId()) {
+ if ((isRestrictedCharUuid(characteristic.getUuid()) ||
+ isRestrictedSrvcUuid(service.getUuid())) &&
+ (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)))
+ return false;
+ else
+ return true;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (GattDebugUtils.handleDebugAction(this, intent)) {
@@ -310,10 +354,12 @@
@Override
public void startScan(int appIf, boolean isServer, ScanSettings settings,
- List<ScanFilter> filters, List storages, String callingPackage) {
+ List<ScanFilter> filters, WorkSource workSource, List storages,
+ String callingPackage) {
GattService service = getService();
if (service == null) return;
- service.startScan(appIf, isServer, settings, filters, storages, callingPackage);
+ service.startScan(appIf, isServer, settings, filters, workSource, storages,
+ callingPackage);
}
public void stopScan(int appIf, boolean isServer) {
@@ -353,55 +399,30 @@
service.discoverServices(clientIf, address);
}
- public void readCharacteristic(int clientIf, String address, int srvcType,
- int srvcInstanceId, ParcelUuid srvcId,
- int charInstanceId, ParcelUuid charId,
- int authReq) {
+ public void readCharacteristic(int clientIf, String address, int handle, int authReq) {
GattService service = getService();
if (service == null) return;
- service.readCharacteristic(clientIf, address, srvcType, srvcInstanceId,
- srvcId.getUuid(), charInstanceId,
- charId.getUuid(), authReq);
+ service.readCharacteristic(clientIf, address, handle, authReq);
}
- public void writeCharacteristic(int clientIf, String address, int srvcType,
- int srvcInstanceId, ParcelUuid srvcId,
- int charInstanceId, ParcelUuid charId,
+ public void writeCharacteristic(int clientIf, String address, int handle,
int writeType, int authReq, byte[] value) {
GattService service = getService();
if (service == null) return;
- service.writeCharacteristic(clientIf, address, srvcType, srvcInstanceId,
- srvcId.getUuid(), charInstanceId,
- charId.getUuid(), writeType, authReq,
- value);
+ service.writeCharacteristic(clientIf, address, handle, writeType, authReq, value);
}
- public void readDescriptor(int clientIf, String address, int srvcType,
- int srvcInstanceId, ParcelUuid srvcId,
- int charInstanceId, ParcelUuid charId,
- int descrInstanceId, ParcelUuid descrId,
- int authReq) {
+ public void readDescriptor(int clientIf, String address, int handle, int authReq) {
GattService service = getService();
if (service == null) return;
- service.readDescriptor(clientIf, address, srvcType,
- srvcInstanceId, srvcId.getUuid(),
- charInstanceId, charId.getUuid(),
- descrInstanceId, descrId.getUuid(),
- authReq);
+ service.readDescriptor(clientIf, address, handle, authReq);
}
- public void writeDescriptor(int clientIf, String address, int srvcType,
- int srvcInstanceId, ParcelUuid srvcId,
- int charInstanceId, ParcelUuid charId,
- int descrInstanceId, ParcelUuid descrId,
- int writeType, int authReq, byte[] value) {
+ public void writeDescriptor(int clientIf, String address, int handle,
+ int writeType, int authReq, byte[] value) {
GattService service = getService();
if (service == null) return;
- service.writeDescriptor(clientIf, address, srvcType,
- srvcInstanceId, srvcId.getUuid(),
- charInstanceId, charId.getUuid(),
- descrInstanceId, descrId.getUuid(),
- writeType, authReq, value);
+ service.writeDescriptor(clientIf, address, handle, writeType, authReq, value);
}
public void beginReliableWrite(int clientIf, String address) {
@@ -416,15 +437,10 @@
service.endReliableWrite(clientIf, address, execute);
}
- public void registerForNotification(int clientIf, String address, int srvcType,
- int srvcInstanceId, ParcelUuid srvcId,
- int charInstanceId, ParcelUuid charId,
- boolean enable) {
+ public void registerForNotification(int clientIf, String address, int handle, boolean enable) {
GattService service = getService();
if (service == null) return;
- service.registerForNotification(clientIf, address, srvcType, srvcInstanceId,
- srvcId.getUuid(), charInstanceId,
- charId.getUuid(), enable);
+ service.registerForNotification(clientIf, address, handle, enable);
}
public void readRemoteRssi(int clientIf, String address) {
@@ -584,6 +600,8 @@
if (VDBG) Log.d(TAG, "onScanResult() - address=" + address
+ ", rssi=" + rssi);
List<UUID> remoteUuids = parseUuids(adv_data);
+ addScanResult();
+
for (ScanClient client : mScanManager.getRegularScanQueue()) {
if (client.uuids.length > 0) {
int matches = 0;
@@ -613,6 +631,7 @@
ScanSettings settings = client.settings;
if ((settings.getCallbackType() &
ScanSettings.CALLBACK_TYPE_ALL_MATCHES) != 0) {
+ app.appScanStats.addResult();
app.callback.onScanResult(result);
}
} catch (RemoteException e) {
@@ -698,7 +717,6 @@
+ ", connId=" + connId + ", address=" + address);
mClientMap.removeConnection(clientIf, connId);
- mSearchQueue.removeConnId(connId);
ClientMap.App app = mClientMap.getById(clientIf);
if (app != null) {
app.callback.onClientConnectionState(status, clientIf, false, address);
@@ -707,170 +725,96 @@
void onSearchCompleted(int connId, int status) throws RemoteException {
if (DBG) Log.d(TAG, "onSearchCompleted() - connId=" + connId+ ", status=" + status);
- // We got all services, now let's explore characteristics...
- continueSearch(connId, status);
+ // Gatt DB is ready!
+ gattClientGetGattDbNative(connId);
}
- void onSearchResult(int connId, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb)
- throws RemoteException {
- UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+ GattDbElement GetSampleGattDbElement() {
+ return new GattDbElement();
+ }
+
+ void onGetGattDb(int connId, ArrayList<GattDbElement> db) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
- if (VDBG) Log.d(TAG, "onSearchResult() - address=" + address + ", uuid=" + uuid);
-
- mSearchQueue.add(connId, srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb);
+ if (DBG) Log.d(TAG, "onGetGattDb() - address=" + address);
ClientMap.App app = mClientMap.getByConnId(connId);
- if (app != null) {
- app.callback.onGetService(address, srvcType, srvcInstId,
- new ParcelUuid(uuid));
+ if (app == null || app.callback == null) {
+ Log.e(TAG, "app or callback is null");
+ return;
}
- }
- void onGetCharacteristic(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
- int charProp) throws RemoteException {
+ List<BluetoothGattService> db_out = new ArrayList<BluetoothGattService>();
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
- String address = mClientMap.addressByConnId(connId);
+ BluetoothGattService currSrvc = null;
+ BluetoothGattCharacteristic currChar = null;
- if (VDBG) Log.d(TAG, "onGetCharacteristic() - address=" + address
- + ", status=" + status + ", charUuid=" + charUuid + ", prop=" + charProp);
+ for (GattDbElement el: db) {
+ switch (el.type)
+ {
+ case GattDbElement.TYPE_PRIMARY_SERVICE:
+ case GattDbElement.TYPE_SECONDARY_SERVICE:
+ if (DBG) Log.d(TAG, "got service with UUID=" + el.uuid);
- if (status == 0) {
- mSearchQueue.add(connId, srvcType,
- srvcInstId, srvcUuidLsb, srvcUuidMsb,
- charInstId, charUuidLsb, charUuidMsb);
+ currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
+ db_out.add(currSrvc);
+ break;
- ClientMap.App app = mClientMap.getByConnId(connId);
- if (app != null) {
- app.callback.onGetCharacteristic(address, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid), charProp);
+ case GattDbElement.TYPE_CHARACTERISTIC:
+ if (DBG) Log.d(TAG, "got characteristic with UUID=" + el.uuid);
+
+ currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
+ currSrvc.addCharacteristic(currChar);
+ break;
+
+ case GattDbElement.TYPE_DESCRIPTOR:
+ if (DBG) Log.d(TAG, "got descriptor with UUID=" + el.uuid);
+
+ currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+ break;
+
+ case GattDbElement.TYPE_INCLUDED_SERVICE:
+ if (DBG) Log.d(TAG, "got included service with UUID=" + el.uuid);
+
+ currSrvc.addIncludedService(new BluetoothGattService(el.uuid, el.id, el.type));
+ break;
+
+ default:
+ Log.e(TAG, "got unknown element with type=" + el.type + " and UUID=" + el.uuid);
}
-
- // Get next characteristic in the current service
- gattClientGetCharacteristicNative(connId, srvcType,
- srvcInstId, srvcUuidLsb, srvcUuidMsb,
- charInstId, charUuidLsb, charUuidMsb);
- } else {
- // Check for included services next
- gattClientGetIncludedServiceNative(connId,
- srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb,
- 0,0,0,0);
}
+
+ // Search is complete when there was error, or nothing more to process
+ gattClientDatabases.put(connId, db_out);
+ app.callback.onSearchComplete(address, db_out, 0 /* status */);
}
- void onGetDescriptor(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
- int descrInstId, long descrUuidLsb, long descrUuidMsb) throws RemoteException {
-
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
- UUID descUuid = new UUID(descrUuidMsb, descrUuidLsb);
- String address = mClientMap.addressByConnId(connId);
-
- if (VDBG) Log.d(TAG, "onGetDescriptor() - address=" + address
- + ", status=" + status + ", descUuid=" + descUuid);
-
- if (status == 0) {
- ClientMap.App app = mClientMap.getByConnId(connId);
- if (app != null) {
- app.callback.onGetDescriptor(address, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid),
- descrInstId, new ParcelUuid(descUuid));
- }
-
- // Get next descriptor for the current characteristic
- gattClientGetDescriptorNative(connId, srvcType,
- srvcInstId, srvcUuidLsb, srvcUuidMsb,
- charInstId, charUuidLsb, charUuidMsb,
- descrInstId, descrUuidLsb, descrUuidMsb);
- } else {
- // Explore the next service
- continueSearch(connId, 0);
- }
- }
-
- void onGetIncludedService(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, int inclSrvcType,
- int inclSrvcInstId, long inclSrvcUuidLsb, long inclSrvcUuidMsb)
- throws RemoteException {
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID inclSrvcUuid = new UUID(inclSrvcUuidMsb, inclSrvcUuidLsb);
- String address = mClientMap.addressByConnId(connId);
-
- if (VDBG) Log.d(TAG, "onGetIncludedService() - address=" + address
- + ", status=" + status + ", uuid=" + srvcUuid
- + ", inclUuid=" + inclSrvcUuid);
-
- if (status == 0) {
- ClientMap.App app = mClientMap.getByConnId(connId);
- if (app != null) {
- app.callback.onGetIncludedService(address,
- srvcType, srvcInstId, new ParcelUuid(srvcUuid),
- inclSrvcType, inclSrvcInstId, new ParcelUuid(inclSrvcUuid));
- }
-
- // Find additional included services
- gattClientGetIncludedServiceNative(connId,
- srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb,
- inclSrvcType, inclSrvcInstId, inclSrvcUuidLsb, inclSrvcUuidMsb);
- } else {
- // Discover descriptors now
- continueSearch(connId, 0);
- }
- }
-
- void onRegisterForNotifications(int connId, int status, int registered, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb) {
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+ void onRegisterForNotifications(int connId, int status, int registered, int handle) {
String address = mClientMap.addressByConnId(connId);
if (DBG) Log.d(TAG, "onRegisterForNotifications() - address=" + address
+ ", status=" + status + ", registered=" + registered
- + ", charUuid=" + charUuid);
+ + ", handle=" + handle);
}
- void onNotify(int connId, String address, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
+ void onNotify(int connId, String address, int handle,
boolean isNotify, byte[] data) throws RemoteException {
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
if (VDBG) Log.d(TAG, "onNotify() - address=" + address
- + ", charUuid=" + charUuid + ", length=" + data.length);
+ + ", handle=" + handle + ", length=" + data.length);
-
- if (isHidUuid(charUuid) &&
- (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED))) {
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "onNotify() - permission check failed!");
return;
}
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
- app.callback.onNotify(address, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid),
- data);
+ app.callback.onNotify(address, handle, data);
}
}
- void onReadCharacteristic(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
- int charType, byte[] data) throws RemoteException {
-
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+ void onReadCharacteristic(int connId, int status, int handle, byte[] data) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
if (VDBG) Log.d(TAG, "onReadCharacteristic() - address=" + address
@@ -878,19 +822,12 @@
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
- app.callback.onCharacteristicRead(address, status, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid), data);
+ app.callback.onCharacteristicRead(address, status, handle, data);
}
}
- void onWriteCharacteristic(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb)
+ void onWriteCharacteristic(int connId, int status, int handle)
throws RemoteException {
-
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
String address = mClientMap.addressByConnId(connId);
if (VDBG) Log.d(TAG, "onWriteCharacteristic() - address=" + address
@@ -900,15 +837,12 @@
if (app == null) return;
if (!app.isCongested) {
- app.callback.onCharacteristicWrite(address, status, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid));
+ app.callback.onCharacteristicWrite(address, status, handle);
} else {
if (status == BluetoothGatt.GATT_CONNECTION_CONGESTED) {
status = BluetoothGatt.GATT_SUCCESS;
}
- CallbackInfo callbackInfo = new CallbackInfo(address, status, srvcType,
- srvcInstId, srvcUuid, charInstId, charUuid);
+ CallbackInfo callbackInfo = new CallbackInfo(address, status, handle);
app.queueCallback(callbackInfo);
}
}
@@ -924,15 +858,7 @@
}
}
- void onReadDescriptor(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
- int descrInstId, long descrUuidLsb, long descrUuidMsb,
- int charType, byte[] data) throws RemoteException {
-
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
- UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb);
+ void onReadDescriptor(int connId, int status, int handle, byte[] data) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
if (VDBG) Log.d(TAG, "onReadDescriptor() - address=" + address
@@ -940,21 +866,11 @@
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
- app.callback.onDescriptorRead(address, status, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid),
- descrInstId, new ParcelUuid(descrUuid), data);
+ app.callback.onDescriptorRead(address, status, handle, data);
}
}
- void onWriteDescriptor(int connId, int status, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb,
- int descrInstId, long descrUuidLsb, long descrUuidMsb) throws RemoteException {
-
- UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
- UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
- UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb);
+ void onWriteDescriptor(int connId, int status, int handle) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
if (VDBG) Log.d(TAG, "onWriteDescriptor() - address=" + address
@@ -962,10 +878,7 @@
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
- app.callback.onDescriptorWrite(address, status, srvcType,
- srvcInstId, new ParcelUuid(srvcUuid),
- charInstId, new ParcelUuid(charUuid),
- descrInstId, new ParcelUuid(descrUuid));
+ app.callback.onDescriptorWrite(address, status, handle);
}
}
@@ -1315,9 +1228,7 @@
CallbackInfo callbackInfo = app.popQueuedCallback();
if (callbackInfo == null) return;
app.callback.onCharacteristicWrite(callbackInfo.address,
- callbackInfo.status, callbackInfo.srvcType,
- callbackInfo.srvcInstId, new ParcelUuid(callbackInfo.srvcUuid),
- callbackInfo.charInstId, new ParcelUuid(callbackInfo.charUuid));
+ callbackInfo.status, callbackInfo.handle);
}
}
}
@@ -1372,19 +1283,44 @@
}
void startScan(int appIf, boolean isServer, ScanSettings settings,
- List<ScanFilter> filters, List<List<ResultStorageDescriptor>> storages,
- String callingPackage) {
+ List<ScanFilter> filters, WorkSource workSource,
+ List<List<ResultStorageDescriptor>> storages, String callingPackage) {
if (DBG) Log.d(TAG, "start scan with filters");
enforceAdminPermission();
if (needsPrivilegedPermissionForScan(settings)) {
enforcePrivilegedPermission();
}
- final ScanClient scanClient = new ScanClient(appIf, isServer, settings, filters, storages);
+ if (workSource != null) {
+ enforceImpersonatationPermission();
+ } else {
+ // Blame the caller if the work source is unspecified.
+ workSource = new WorkSource(Binder.getCallingUid(), callingPackage);
+ }
+ final ScanClient scanClient = new ScanClient(appIf, isServer, settings, filters, workSource,
+ storages);
scanClient.hasLocationPermission = Utils.checkCallerHasLocationPermission(this, mAppOps,
callingPackage);
scanClient.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(
this);
scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
+
+ AppScanStats app = null;
+ if (isServer) {
+ app = mServerMap.getAppScanStatsById(appIf);
+ } else {
+ app = mClientMap.getAppScanStatsById(appIf);
+ }
+
+ if (app != null) {
+ if (app.isScanningTooFrequently() &&
+ checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) {
+ Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
+ return;
+ }
+ scanClient.stats = app;
+ app.recordScanStart(settings);
+ }
+
mScanManager.startScan(scanClient);
}
@@ -1399,6 +1335,15 @@
int scanQueueSize = mScanManager.getBatchScanQueue().size() +
mScanManager.getRegularScanQueue().size();
if (DBG) Log.d(TAG, "stopScan() - queue size =" + scanQueueSize);
+
+ AppScanStats app = null;
+ if (client.isServer) {
+ app = mServerMap.getAppScanStatsById(client.clientIf);
+ } else {
+ app = mClientMap.getAppScanStatsById(client.clientIf);
+ }
+ if (app != null) app.recordScanStop();
+
mScanManager.stopScan(client);
}
@@ -1427,7 +1372,7 @@
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);
- mClientMap.add(uuid, callback);
+ mClientMap.add(uuid, callback, this);
gattClientRegisterAppNative(uuid.getLeastSignificantBits(),
uuid.getMostSignificantBits());
}
@@ -1511,93 +1456,83 @@
Log.e(TAG, "discoverServices() - No connection for " + address + "...");
}
- void readCharacteristic(int clientIf, String address, int srvcType,
- int srvcInstanceId, UUID srvcUuid,
- int charInstanceId, UUID charUuid, int authReq) {
+ void readCharacteristic(int clientIf, String address, int handle, int authReq) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (isHidUuid(charUuid)) enforcePrivilegedPermission();
if (VDBG) Log.d(TAG, "readCharacteristic() - address=" + address);
Integer connId = mClientMap.connIdByAddress(clientIf, address);
- if (connId != null)
- gattClientReadCharacteristicNative(connId, srvcType,
- srvcInstanceId, srvcUuid.getLeastSignificantBits(),
- srvcUuid.getMostSignificantBits(), charInstanceId,
- charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
- authReq);
- else
+ if (connId == null) {
Log.e(TAG, "readCharacteristic() - No connection for " + address + "...");
+ return;
+ }
+
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "readCharacteristic() - permission check failed!");
+ return;
+ }
+
+ gattClientReadCharacteristicNative(connId, handle, authReq);
}
- void writeCharacteristic(int clientIf, String address, int srvcType,
- int srvcInstanceId, UUID srvcUuid,
- int charInstanceId, UUID charUuid, int writeType,
+ void writeCharacteristic(int clientIf, String address, int handle, int writeType,
int authReq, byte[] value) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (isHidUuid(charUuid)) enforcePrivilegedPermission();
if (VDBG) Log.d(TAG, "writeCharacteristic() - address=" + address);
if (mReliableQueue.contains(address)) writeType = 3; // Prepared write
Integer connId = mClientMap.connIdByAddress(clientIf, address);
- if (connId != null)
- gattClientWriteCharacteristicNative(connId, srvcType,
- srvcInstanceId, srvcUuid.getLeastSignificantBits(),
- srvcUuid.getMostSignificantBits(), charInstanceId,
- charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
- writeType, authReq, value);
- else
+ if (connId == null) {
Log.e(TAG, "writeCharacteristic() - No connection for " + address + "...");
+ return;
+ }
+
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "writeCharacteristic() - permission check failed!");
+ return;
+ }
+
+ gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
}
- void readDescriptor(int clientIf, String address, int srvcType,
- int srvcInstanceId, UUID srvcUuid,
- int charInstanceId, UUID charUuid,
- int descrInstanceId, UUID descrUuid,
- int authReq) {
+ void readDescriptor(int clientIf, String address, int handle, int authReq) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (isHidUuid(charUuid)) enforcePrivilegedPermission();
if (VDBG) Log.d(TAG, "readDescriptor() - address=" + address);
Integer connId = mClientMap.connIdByAddress(clientIf, address);
- if (connId != null)
- gattClientReadDescriptorNative(connId, srvcType,
- srvcInstanceId,
- srvcUuid.getLeastSignificantBits(), srvcUuid.getMostSignificantBits(),
- charInstanceId,
- charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
- descrInstanceId,
- descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(),
- authReq);
- else
+ if (connId == null) {
Log.e(TAG, "readDescriptor() - No connection for " + address + "...");
+ return;
+ }
+
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "readDescriptor() - permission check failed!");
+ return;
+ }
+
+ gattClientReadDescriptorNative(connId, handle, authReq);
};
- void writeDescriptor(int clientIf, String address, int srvcType,
- int srvcInstanceId, UUID srvcUuid,
- int charInstanceId, UUID charUuid,
- int descrInstanceId, UUID descrUuid,
+ void writeDescriptor(int clientIf, String address, int handle,
int writeType, int authReq, byte[] value) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (isHidUuid(charUuid)) enforcePrivilegedPermission();
-
if (VDBG) Log.d(TAG, "writeDescriptor() - address=" + address);
Integer connId = mClientMap.connIdByAddress(clientIf, address);
- if (connId != null)
- gattClientWriteDescriptorNative(connId, srvcType,
- srvcInstanceId,
- srvcUuid.getLeastSignificantBits(), srvcUuid.getMostSignificantBits(),
- charInstanceId,
- charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
- descrInstanceId,
- descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(),
- writeType, authReq, value);
- else
+ if (connId == null) {
Log.e(TAG, "writeDescriptor() - No connection for " + address + "...");
+ return;
+ }
+
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "writeDescriptor() - permission check failed!");
+ return;
+ }
+
+ gattClientWriteDescriptorNative(connId, handle, writeType, authReq, value);
}
void beginReliableWrite(int clientIf, String address) {
@@ -1618,25 +1553,23 @@
if (connId != null) gattClientExecuteWriteNative(connId, execute);
}
- void registerForNotification(int clientIf, String address, int srvcType,
- int srvcInstanceId, UUID srvcUuid,
- int charInstanceId, UUID charUuid,
- boolean enable) {
+ void registerForNotification(int clientIf, String address, int handle, boolean enable) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (isHidUuid(charUuid)) enforcePrivilegedPermission();
if (DBG) Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable);
Integer connId = mClientMap.connIdByAddress(clientIf, address);
- if (connId != null) {
- gattClientRegisterForNotificationsNative(clientIf, address,
- srvcType, srvcInstanceId, srvcUuid.getLeastSignificantBits(),
- srvcUuid.getMostSignificantBits(), charInstanceId,
- charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
- enable);
- } else {
+ if (connId == null) {
Log.e(TAG, "registerForNotification() - No connection for " + address + "...");
+ return;
}
+
+ if (!permissionCheck(connId, handle)) {
+ Log.w(TAG, "registerForNotification() - permission check failed!");
+ return;
+ }
+
+ gattClientRegisterForNotificationsNative(clientIf, address, handle, enable);
}
void readRemoteRssi(int clientIf, String address) {
@@ -1955,7 +1888,7 @@
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (DBG) Log.d(TAG, "registerServer() - UUID=" + uuid);
- mServerMap.add(uuid, callback);
+ mServerMap.add(uuid, callback, this);
gattServerRegisterAppNative(uuid.getLeastSignificantBits(),
uuid.getMostSignificantBits());
}
@@ -2097,6 +2030,14 @@
* Private functions
*************************************************************************/
+ private boolean isRestrictedCharUuid(final UUID charUuid) {
+ return isHidUuid(charUuid);
+ }
+
+ private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
+ return isFidoUUID(srvcUuid);
+ }
+
private boolean isHidUuid(final UUID uuid) {
for (UUID hid_uuid : HID_UUIDS) {
if (hid_uuid.equals(uuid)) return true;
@@ -2104,6 +2045,13 @@
return false;
}
+ private boolean isFidoUUID(final UUID uuid) {
+ for (UUID fido_uuid : FIDO_UUIDS) {
+ if (fido_uuid.equals(uuid)) return true;
+ }
+ return false;
+ }
+
private int getDeviceType(BluetoothDevice device) {
int type = gattClientGetDeviceTypeNative(device.getAddress());
if (DBG) Log.d(TAG, "getDeviceType() - device=" + device
@@ -2137,26 +2085,12 @@
"Need BLUETOOTH_PRIVILEGED permission");
}
- private void continueSearch(int connId, int status) throws RemoteException {
- if (status == 0 && !mSearchQueue.isEmpty()) {
- SearchQueue.Entry svc = mSearchQueue.pop();
-
- if (svc.charUuidLsb == 0) {
- // Characteristic is up next
- gattClientGetCharacteristicNative(svc.connId, svc.srvcType,
- svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb, 0, 0, 0);
- } else {
- // Descriptor is up next
- gattClientGetDescriptorNative(svc.connId, svc.srvcType,
- svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb,
- svc.charInstId, svc.charUuidLsb, svc.charUuidMsb, 0, 0, 0);
- }
- } else {
- ClientMap.App app = mClientMap.getByConnId(connId);
- if (app != null) {
- app.callback.onSearchComplete(mClientMap.addressByConnId(connId), status);
- }
- }
+ // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other
+ // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does
+ // not have UPDATE_DEVICE_STATS permission.
+ private void enforceImpersonatationPermission() {
+ enforceCallingOrSelfPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+ "Need UPDATE_DEVICE_STATS permission");
}
private void continueServiceDeclaration(int serverIf, int status, int srvcHandle) throws RemoteException {
@@ -2323,13 +2257,38 @@
sb.append("\nGATT Client Map\n");
mClientMap.dump(sb);
- sb.append("\nGATT Server Map\n");
+ sb.append("GATT Server Map\n");
mServerMap.dump(sb);
- sb.append("\nGATT Handle Map\n");
+ sb.append("GATT Handle Map\n");
mHandleMap.dump(sb);
}
+ void addScanResult() {
+ if (mScanEvents.isEmpty())
+ return;
+
+ BluetoothProto.ScanEvent curr = mScanEvents.get(mScanEvents.size() - 1);
+ curr.setNumberResults(curr.getNumberResults() + 1);
+ }
+
+ void addScanEvent(BluetoothProto.ScanEvent event) {
+ synchronized(mScanEvents) {
+ if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT)
+ mScanEvents.remove(0);
+ mScanEvents.add(event);
+ }
+ }
+
+ @Override
+ public void dumpProto(BluetoothProto.BluetoothLog proto) {
+ synchronized(mScanEvents) {
+ for (BluetoothProto.ScanEvent event : mScanEvents) {
+ proto.addScanEvent(event);
+ }
+ }
+ }
+
/**************************************************************************
* GATT Test functions
*************************************************************************/
@@ -2374,51 +2333,22 @@
private native void gattClientSearchServiceNative(int conn_id,
boolean search_all, long service_uuid_lsb, long service_uuid_msb);
- private native void gattClientGetCharacteristicNative(int conn_id,
- int service_type, int service_id_inst_id, long service_id_uuid_lsb,
- long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
- long char_id_uuid_msb);
+ private native void gattClientGetGattDbNative(int conn_id);
- private native void gattClientGetDescriptorNative(int conn_id, int service_type,
- int service_id_inst_id, long service_id_uuid_lsb, long service_id_uuid_msb,
- int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb,
- int descr_id_inst_id, long descr_id_uuid_lsb, long descr_id_uuid_msb);
+ private native void gattClientReadCharacteristicNative(int conn_id, int handle, int authReq);
- private native void gattClientGetIncludedServiceNative(int conn_id,
- int service_type, int service_id_inst_id,
- long service_id_uuid_lsb, long service_id_uuid_msb,
- int incl_service_id_inst_id, int incl_service_type,
- long incl_service_id_uuid_lsb, long incl_service_id_uuid_msb);
-
- private native void gattClientReadCharacteristicNative(int conn_id,
- int service_type, int service_id_inst_id, long service_id_uuid_lsb,
- long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
- long char_id_uuid_msb, int authReq);
-
- private native void gattClientReadDescriptorNative(int conn_id, int service_type,
- int service_id_inst_id, long service_id_uuid_lsb, long service_id_uuid_msb,
- int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb,
- int descr_id_inst_id, long descr_id_uuid_lsb, long descr_id_uuid_msb,
- int authReq);
+ private native void gattClientReadDescriptorNative(int conn_id, int handle, int authReq);
private native void gattClientWriteCharacteristicNative(int conn_id,
- int service_type, int service_id_inst_id, long service_id_uuid_lsb,
- long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
- long char_id_uuid_msb, int write_type, int auth_req, byte[] value);
+ int handle, int write_type, int auth_req, byte[] value);
- private native void gattClientWriteDescriptorNative(int conn_id, int service_type,
- int service_id_inst_id, long service_id_uuid_lsb, long service_id_uuid_msb,
- int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb,
- int descr_id_inst_id, long descr_id_uuid_lsb, long descr_id_uuid_msb,
+ private native void gattClientWriteDescriptorNative(int conn_id, int handle,
int write_type, int auth_req, byte[] value);
private native void gattClientExecuteWriteNative(int conn_id, boolean execute);
private native void gattClientRegisterForNotificationsNative(int clientIf,
- String address, int service_type, int service_id_inst_id,
- long service_id_uuid_lsb, long service_id_uuid_msb,
- int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb,
- boolean enable);
+ String address, int handle, boolean enable);
private native void gattClientReadRemoteRssiNative(int clientIf,
String address);
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index 64d3e1f..a620436 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -19,6 +19,7 @@
import android.bluetooth.le.ResultStorageDescriptor;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
+import android.os.WorkSource;
import java.util.List;
import java.util.Objects;
@@ -43,34 +44,47 @@
// Pre-M apps are allowed to get scan results even if location is disabled
boolean legacyForegroundApp;
+ // Who is responsible for this scan.
+ WorkSource workSource;
+
+ AppScanStats stats = null;
+
private static final ScanSettings DEFAULT_SCAN_SETTINGS = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
ScanClient(int appIf, boolean isServer) {
- this(appIf, isServer, new UUID[0], DEFAULT_SCAN_SETTINGS, null, null);
+ this(appIf, isServer, new UUID[0], DEFAULT_SCAN_SETTINGS, null, null, null);
}
ScanClient(int appIf, boolean isServer, UUID[] uuids) {
- this(appIf, isServer, uuids, DEFAULT_SCAN_SETTINGS, null, null);
+ this(appIf, isServer, uuids, DEFAULT_SCAN_SETTINGS, null, null, null);
}
ScanClient(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters) {
- this(appIf, isServer, new UUID[0], settings, filters, null);
+ this(appIf, isServer, new UUID[0], settings, filters, null, null);
}
ScanClient(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters, List<List<ResultStorageDescriptor>> storages) {
- this(appIf, isServer, new UUID[0], settings, filters, storages);
+ this(appIf, isServer, new UUID[0], settings, filters, null, storages);
+ }
+
+ ScanClient(int appIf, boolean isServer, ScanSettings settings,
+ List<ScanFilter> filters, WorkSource workSource,
+ List<List<ResultStorageDescriptor>> storages) {
+ this(appIf, isServer, new UUID[0], settings, filters, workSource, storages);
}
private ScanClient(int appIf, boolean isServer, UUID[] uuids, ScanSettings settings,
- List<ScanFilter> filters, List<List<ResultStorageDescriptor>> storages) {
+ List<ScanFilter> filters, WorkSource workSource,
+ List<List<ResultStorageDescriptor>> storages) {
this.clientIf = appIf;
this.isServer = isServer;
this.uuids = uuids;
this.settings = settings;
this.filters = filters;
+ this.workSource = workSource;
this.storages = storages;
}
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 4b47753..3dab4af 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -61,8 +61,10 @@
@Override
public int hashCode() {
- return Objects.hash(address, addr_type, type, uuid, uuid_mask, name, company,
- company_mask, data, data_mask);
+ return Objects.hash(address, addr_type, type, uuid, uuid_mask,
+ name, company, company_mask,
+ Arrays.hashCode(data),
+ Arrays.hashCode(data_mask));
}
@Override
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 4ddbac0..4144155 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -31,11 +31,13 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.app.IBatteryStats;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -64,6 +66,10 @@
private static final int MSG_START_BLE_SCAN = 0;
private static final int MSG_STOP_BLE_SCAN = 1;
private static final int MSG_FLUSH_BATCH_RESULTS = 2;
+ private static final int MSG_SCAN_TIMEOUT = 3;
+
+ // Maximum msec before scan gets downgraded to opportunistic
+ private static final int SCAN_TIMEOUT_MS = 5 * 60 * 1000;
private static final String ACTION_REFRESH_BATCHED_SCAN =
"com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
@@ -77,6 +83,7 @@
private Integer curUsedTrackableAdvertisements;
private GattService mService;
+ private IBatteryStats mBatteryStats;
private BroadcastReceiver mBatchAlarmReceiver;
private boolean mBatchAlarmReceiverRegistered;
private ScanNative mScanNative;
@@ -96,6 +103,7 @@
}
void start() {
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batterystats"));
HandlerThread thread = new HandlerThread("BluetoothScanManager");
thread.start();
mHandler = new ClientHandler(thread.getLooper());
@@ -188,6 +196,9 @@
case MSG_FLUSH_BATCH_RESULTS:
handleFlushBatchResults(client);
break;
+ case MSG_SCAN_TIMEOUT:
+ mScanNative.regularScanTimeout();
+ break;
default:
// Shouldn't happen.
Log.e(TAG, "received an unkown message : " + msg.what);
@@ -216,6 +227,21 @@
mScanNative.startRegularScan(client);
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
+
+ if (!mScanNative.isFirstMatchScanClient(client)) {
+ Message msg = mHandler.obtainMessage(MSG_SCAN_TIMEOUT);
+ msg.obj = client;
+ // Only one timeout message should exist at any time
+ mHandler.removeMessages(SCAN_TIMEOUT_MS);
+ mHandler.sendMessageDelayed(msg, SCAN_TIMEOUT_MS);
+ }
+ }
+
+ // Update BatteryStats with this workload.
+ try {
+ mBatteryStats.noteBleScanStarted(client.workSource);
+ } catch (RemoteException e) {
+ /* ignore */
}
}
}
@@ -223,11 +249,29 @@
void handleStopScan(ScanClient client) {
Utils.enforceAdminPermission(mService);
if (client == null) return;
+
if (mRegularScanClients.contains(client)) {
+ // The ScanClient passed in just holds the clientIf. We retrieve the real client,
+ // which may have workSource set.
+ client = mScanNative.getRegularScanClient(client.clientIf);
+ if (client == null) return;
+
mScanNative.stopRegularScan(client);
+
+ if (mScanNative.numRegularScanClients() == 0) {
+ mHandler.removeMessages(MSG_SCAN_TIMEOUT);
+ }
+
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
}
+
+ // Update BatteryStats with this workload.
+ try {
+ mBatteryStats.noteBleScanStopped(client.workSource);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
} else {
mScanNative.stopBatchScan(client);
}
@@ -484,6 +528,10 @@
return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
}
+ private boolean isFirstMatchScanClient(ScanClient client) {
+ return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+ }
+
private void resetBatchScan(ScanClient client) {
int clientIf = client.clientIf;
BatchScanParams batchScanParams = getBatchScanParams();
@@ -598,7 +646,6 @@
void stopRegularScan(ScanClient client) {
// Remove scan filters and recycle filter indices.
- client = getClient(client.clientIf);
if (client == null) return;
int deliveryMode = getDeliveryMode(client);
if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
@@ -624,8 +671,38 @@
removeScanFilters(client.clientIf);
}
- // Find the scan client information
- ScanClient getClient(int clientIf) {
+ void regularScanTimeout() {
+ for (ScanClient client : mRegularScanClients) {
+ if (!isOpportunisticScanClient(client) && !isFirstMatchScanClient(client)) {
+ logd("clientIf set to scan opportunisticly: " + client.clientIf);
+ setOpportunisticScanClient(client);
+ client.stats.setScanTimeout();
+ }
+ }
+
+ // The scan should continue for background scans
+ configureRegularScanParams();
+ if (numRegularScanClients() == 0) {
+ logd("stop scan");
+ gattClientScanNative(false);
+ }
+ }
+
+ void setOpportunisticScanClient(ScanClient client) {
+ // TODO: Add constructor to ScanSettings.Builder
+ // that can copy values from an existing ScanSettings object
+ ScanSettings.Builder builder = new ScanSettings.Builder();
+ ScanSettings settings = client.settings;
+ builder.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC);
+ builder.setCallbackType(settings.getCallbackType());
+ builder.setScanResultType(settings.getScanResultType());
+ builder.setReportDelay(settings.getReportDelayMillis());
+ builder.setNumOfMatches(settings.getNumOfMatches());
+ client.settings = builder.build();
+ }
+
+ // Find the regular scan client information.
+ ScanClient getRegularScanClient(int clientIf) {
for (ScanClient client : mRegularScanClients) {
if (client.clientIf == clientIf) return client;
}
diff --git a/src/com/android/bluetooth/gatt/SearchQueue.java b/src/com/android/bluetooth/gatt/SearchQueue.java
deleted file mode 100644
index 3064a51..0000000
--- a/src/com/android/bluetooth/gatt/SearchQueue.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.gatt;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Helper class to store characteristics and descriptors that will be
- * queued up for future exploration.
- * @hide
- */
-/*package*/ class SearchQueue {
- class Entry {
- public int connId;
- public int srvcType;
- public int srvcInstId;
- public long srvcUuidLsb;
- public long srvcUuidMsb;
- public int charInstId;
- public long charUuidLsb;
- public long charUuidMsb;
- }
-
- private List<Entry> mEntries = new ArrayList<Entry>();
-
- void add(int connId, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb) {
- Entry entry = new Entry();
- entry.connId = connId;
- entry.srvcType = srvcType;
- entry.srvcInstId = srvcInstId;
- entry.srvcUuidLsb = srvcUuidLsb;
- entry.srvcUuidMsb = srvcUuidMsb;
- entry.charUuidLsb = 0;
- mEntries.add(entry);
- }
-
- void add(int connId, int srvcType,
- int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
- int charInstId, long charUuidLsb, long charUuidMsb)
- {
- Entry entry = new Entry();
- entry.connId = connId;
- entry.srvcType = srvcType;
- entry.srvcInstId = srvcInstId;
- entry.srvcUuidLsb = srvcUuidLsb;
- entry.srvcUuidMsb = srvcUuidMsb;
- entry.charInstId = charInstId;
- entry.charUuidLsb = charUuidLsb;
- entry.charUuidMsb = charUuidMsb;
- mEntries.add(entry);
- }
-
- Entry pop() {
- Entry entry = mEntries.get(0);
- mEntries.remove(0);
- return entry;
- }
-
- void removeConnId(int connId) {
- for (Iterator<Entry> it = mEntries.iterator(); it.hasNext();) {
- Entry entry = it.next();
- if (entry.connId == connId) {
- it.remove();
- }
- }
- }
-
- boolean isEmpty() {
- return mEntries.isEmpty();
- }
-
- void clear() {
- mEntries.clear();
- }
-}
diff --git a/src/com/android/bluetooth/gatt/ServiceDeclaration.java b/src/com/android/bluetooth/gatt/ServiceDeclaration.java
index 1d0bc4e..37316b1 100644
--- a/src/com/android/bluetooth/gatt/ServiceDeclaration.java
+++ b/src/com/android/bluetooth/gatt/ServiceDeclaration.java
@@ -15,9 +15,7 @@
*/
package com.android.bluetooth.gatt;
-import android.util.Log;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -49,11 +47,11 @@
}
Entry(UUID uuid, int serviceType, int instance, boolean advertisePreferred) {
- this.type = TYPE_SERVICE;
- this.uuid = uuid;
- this.instance = instance;
- this.serviceType = serviceType;
- this.advertisePreferred = advertisePreferred;
+ this.type = TYPE_SERVICE;
+ this.uuid = uuid;
+ this.instance = instance;
+ this.serviceType = serviceType;
+ this.advertisePreferred = advertisePreferred;
}
Entry(UUID uuid, int properties, int permissions, int instance) {
@@ -71,57 +69,69 @@
}
}
- List<Entry> mEntries = null;
- int mNumHandles = 0;
-
- ServiceDeclaration() {
- mEntries = new ArrayList<Entry>();
- }
+ // guards access to mEntries and mNumHandles in order to make this class thread-safe
+ private final Object mLock = new Object();
+ private final List<Entry> mEntries = new ArrayList<>();
+ private int mNumHandles = 0;
void addService(UUID uuid, int serviceType, int instance, int minHandles,
- boolean advertisePreferred) {
- mEntries.add(new Entry(uuid, serviceType, instance, advertisePreferred));
- if (minHandles == 0) {
- ++mNumHandles;
- } else {
- mNumHandles = minHandles;
+ boolean advertisePreferred) {
+ synchronized (mLock) {
+ mEntries.add(new Entry(uuid, serviceType, instance, advertisePreferred));
+ if (minHandles == 0) {
+ ++mNumHandles;
+ } else {
+ mNumHandles = minHandles;
+ }
}
}
void addIncludedService(UUID uuid, int serviceType, int instance) {
Entry entry = new Entry(uuid, serviceType, instance);
entry.type = TYPE_INCLUDED_SERVICE;
- mEntries.add(entry);
- ++mNumHandles;
+ synchronized (mLock) {
+ mEntries.add(entry);
+ ++mNumHandles;
+ }
}
void addCharacteristic(UUID uuid, int properties, int permissions) {
- mEntries.add(new Entry(uuid, properties, permissions, 0 /*instance*/));
- mNumHandles += 2;
+ synchronized (mLock) {
+ mEntries.add(new Entry(uuid, properties, permissions, 0 /*instance*/));
+ mNumHandles += 2;
+ }
}
void addDescriptor(UUID uuid, int permissions) {
- mEntries.add(new Entry(uuid, permissions));
- ++mNumHandles;
+ synchronized (mLock) {
+ mEntries.add(new Entry(uuid, permissions));
+ ++mNumHandles;
+ }
}
Entry getNext() {
- if (mEntries.isEmpty()) return null;
- Entry entry = mEntries.get(0);
- mEntries.remove(0);
- return entry;
+ synchronized (mLock) {
+ if (mEntries.isEmpty()) return null;
+ Entry entry = mEntries.get(0);
+ mEntries.remove(0);
+ return entry;
+ }
}
boolean isServiceAdvertisePreferred(UUID uuid) {
- for (Entry entry : mEntries) {
- if (entry.uuid.equals(uuid)) {
- return entry.advertisePreferred;
- }
- }
- return false;
+ synchronized (mLock) {
+ for (Entry entry : mEntries) {
+ if (entry.uuid.equals(uuid)) {
+ return entry.advertisePreferred;
+ }
+ }
+ return false;
+ }
}
int getNumHandles() {
- return mNumHandles;
+ synchronized (mLock) {
+ return mNumHandles;
+ }
}
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 33aea70..bc9f56f 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -117,9 +117,9 @@
}
private void startListenForPhoneState() {
- if (!mListening && mSlcReady) {
+ if (!mListening && mSlcReady && mTelephonyManager != null) {
- int subId = SubscriptionManager.getDefaultSubId();
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
mPhoneStateListener = getPhoneStateListener(subId);
@@ -133,7 +133,7 @@
}
private void stopListenForPhoneState() {
- if (mListening) {
+ if (mListening && mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mListening = false;
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index cda51fc..10bf23a 100755
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -78,7 +78,9 @@
} catch (Exception e) {
Log.w(TAG,"Unable to unregister headset receiver",e);
}
- mStateMachine.doQuit();
+ if (mStateMachine != null) {
+ mStateMachine.doQuit();
+ }
return true;
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index fc55bae..66d75d3 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -2183,9 +2183,7 @@
mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition +
" isInCall: " + isInCall());
if (state == HeadsetHalConstants.VR_STATE_STARTED) {
- if (!isVirtualCallInProgress() &&
- !isInCall())
- {
+ if (!isVirtualCallInProgress() && !isInCall()) {
IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
if (dic != null) {
@@ -2202,6 +2200,11 @@
return;
}
expectVoiceRecognition(device);
+ } else {
+ // send error response if call is ongoing
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
+ return;
}
} else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index b0e026b..8945490 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -32,10 +32,12 @@
import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.List;
+
/**
* Provides Bluetooth Headset Client (HF Role) profile, as a service in the
* Bluetooth application.
@@ -49,6 +51,8 @@
private HeadsetClientStateMachine mStateMachine;
private static HeadsetClientService sHeadsetClientService;
+ public static String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
+
@Override
protected String getName() {
return TAG;
@@ -70,6 +74,12 @@
Log.w(TAG, "Unable to register broadcat receiver", e);
}
setHeadsetClientService(this);
+
+ // Start the HfpClientConnectionService to create connection with telecom when HFP
+ // connection is available.
+ Intent startIntent = new Intent(this, HfpClientConnectionService.class);
+ startService(startIntent);
+
return true;
}
@@ -81,6 +91,12 @@
Log.w(TAG, "Unable to unregister broadcast receiver", e);
}
mStateMachine.doQuit();
+
+ // Stop the HfpClientConnectionService.
+ Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
+ stopIntent.putExtra(HFP_CLIENT_STOP_TAG, true);
+ startService(stopIntent);
+
return true;
}
@@ -98,9 +114,16 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ // We handle the volume changes for Voice calls here since HFP audio volume control does
+ // not go through audio manager (audio mixer). We check if the voice call volume has
+ // changed and subsequently change the SCO volume see
+ // ({@link HeadsetClientStateMachine#SET_SPEAKER_VOLUME} in
+ // {@link HeadsetClientStateMachine} for details.
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ Log.d(TAG, "Volume changed for stream: " +
+ intent.getExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE));
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
+ if (streamType == AudioManager.STREAM_VOICE_CALL) {
int streamValue = intent
.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
int streamPrevValue = intent.getIntExtra(
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 9ae7ba3..3074279 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -51,13 +51,15 @@
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.telecom.TelecomManager;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import java.util.ArrayList;
import java.util.Arrays;
@@ -105,6 +107,9 @@
// special action to handle terminating specific call from multiparty call
static final int TERMINATE_SPECIFIC_CALL = 53;
+ static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
+ static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
+
private static final int STACK_EVENT = 100;
private final Disconnected mDisconnected;
@@ -135,6 +140,9 @@
private int mVoiceRecognitionActive;
private int mInBandRingtone;
+ private int mMaxAmVcVol;
+ private int mMinAmVcVol;
+
// queue of send actions (pair action, action_data)
private Queue<Pair<Integer, Object>> mQueuedActions;
@@ -149,6 +157,7 @@
private boolean mAudioWbs;
private final BluetoothAdapter mAdapter;
private boolean mNativeAvailable;
+ private TelecomManager mTelecomManager;
// currently connected device
private BluetoothDevice mCurrentDevice = null;
@@ -307,20 +316,12 @@
if (state == c.getState()) {
return;
}
- //abandon focus here
- if (state == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
- if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
- Log.d(TAG, "abandonAudioFocus ");
- // abandon audio focus after the mode has been set back to normal
- mAudioManager.abandonAudioFocusForCall();
- }
- }
c.setState(state);
sendCallChangedIntent(c);
}
private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {
+ Log.d(TAG, "sendCallChangedIntent " + c);
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
@@ -1217,6 +1218,8 @@
mAudioRouteAllowed = context.getResources().getBoolean(
R.bool.headset_client_initial_audio_route_allowed);
+ mTelecomManager = (TelecomManager) context.getSystemService(context.TELECOM_SERVICE);
+
mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
mIndicatorNetworkSignal = 0;
@@ -1227,6 +1230,9 @@
mIndicatorCallSetup = -1;
mIndicatorCallHeld = -1;
+ mMaxAmVcVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
+ mMinAmVcVol = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);
+
mOperatorName = null;
mSubscriberInfo = null;
@@ -1274,6 +1280,25 @@
}
}
+ private int hfToAmVol(int hfVol) {
+ int amRange = mMaxAmVcVol - mMinAmVcVol;
+ int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
+ int amOffset =
+ (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange;
+ int amVol = mMinAmVcVol + amOffset;
+ Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
+ return amVol;
+ }
+
+ private int amToHfVol(int amVol) {
+ int amRange = mMaxAmVcVol - mMinAmVcVol;
+ int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
+ int hfOffset = (hfRange * (amVol - mMinAmVcVol)) / amRange;
+ int hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
+ Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
+ return hfVol;
+ }
+
private class Disconnected extends State {
@Override
public void enter() {
@@ -1493,9 +1518,11 @@
}
transitionTo(mConnected);
- // TODO get max stream volume and scale 0-15
- sendMessage(obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME,
- mAudioManager.getStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO), 0));
+ int amVol = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+ sendMessage(
+ obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME, amVol, 0));
+ // Mic is either in ON state (full volume) or OFF state. There is no way in
+ // Android to change the MIC volume.
sendMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
mAudioManager.isMicrophoneMute() ? 0 : 15, 0));
@@ -1615,6 +1642,7 @@
}
}
break;
+ // Called only for Mute/Un-mute - Mic volume change is not allowed.
case SET_MIC_VOLUME:
if (mVgmFromStack) {
mVgmFromStack = false;
@@ -1625,13 +1653,16 @@
}
break;
case SET_SPEAKER_VOLUME:
- Log.d(TAG,"Volume is set to " + message.arg1);
- mAudioManager.setParameters("hfp_volume=" + message.arg1);
+ // This message should always contain the volume in AudioManager max normalized.
+ int amVol = message.arg1;
+ int hfVol = amToHfVol(amVol);
+ Log.d(TAG,"HF volume is set to " + hfVol);
+ mAudioManager.setParameters("hfp_volume=" + hfVol);
if (mVgsFromStack) {
mVgsFromStack = false;
break;
}
- if (setVolumeNative(HeadsetClientHalConstants.VOLUME_TYPE_SPK, message.arg1)) {
+ if (setVolumeNative(HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
addQueuedAction(SET_SPEAKER_VOLUME);
}
break;
@@ -1841,11 +1872,17 @@
break;
case EVENT_TYPE_VOLUME_CHANGED:
if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO,
- event.valueInt2, AudioManager.FLAG_SHOW_UI);
+ Log.d(TAG, "AM volume set to " +
+ hfToAmVol(event.valueInt2));
+ mAudioManager.setStreamVolume(
+ AudioManager.STREAM_VOICE_CALL,
+ hfToAmVol(event.valueInt2),
+ AudioManager.FLAG_SHOW_UI);
mVgsFromStack = true;
- } else if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
+ } else if (event.valueInt ==
+ HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
mAudioManager.setMicrophoneMute(event.valueInt2 == 0);
+
mVgmFromStack = true;
}
break;
@@ -1965,6 +2002,9 @@
mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
break;
case EVENT_TYPE_RING_INDICATION:
+ // Ringing is not handled at this indication and rather should be
+ // implemented (by the client of this service). Use the
+ // CALL_STATE_INCOMING (and similar) handle ringing.
break;
default:
Log.e(TAG, "Unknown stack event: " + event.type);
@@ -2028,23 +2068,21 @@
break;
}
+ // Audio state is split in two parts, the audio focus is maintained by the
+ // entity exercising this service (typically the Telecom stack) and audio
+ // routing is handled by the bluetooth stack itself. The only reason to do so is
+ // because Bluetooth SCO connection from the HF role is not entirely supported
+ // for routing and volume purposes.
+ // NOTE: All calls here are routed via the setParameters which changes the
+ // routing at the Audio HAL level.
mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED;
- // request audio focus for call
- int newAudioMode = AudioManager.MODE_IN_CALL;
- int currMode = mAudioManager.getMode();
- if (currMode != newAudioMode) {
- // request audio focus before setting the new mode
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- Log.d(TAG, "setAudioMode Setting audio mode from "
- + currMode + " to " + newAudioMode);
- mAudioManager.setMode(newAudioMode);
- }
// We need to set the volume after switching into HFP mode as some Audio HALs
// reset the volume to a known-default on mode switch.
- final int volume =
- mAudioManager.getStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO);
+ final int amVol =
+ mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+ final int hfVol = amToHfVol(amVol);
+
Log.d(TAG,"hfp_enable=true");
Log.d(TAG,"mAudioWbs is " + mAudioWbs);
if (mAudioWbs) {
@@ -2055,8 +2093,9 @@
Log.d(TAG,"Setting sampling rate as 8000");
mAudioManager.setParameters("hfp_set_sampling_rate=8000");
}
+ Log.d(TAG, "hf_volume " + hfVol);
mAudioManager.setParameters("hfp_enable=true");
- mAudioManager.setParameters("hfp_volume=" + volume);
+ mAudioManager.setParameters("hfp_volume=" + hfVol);
transitionTo(mAudioOn);
break;
case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
@@ -2088,9 +2127,6 @@
@Override
public void enter() {
Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
-
- mAudioManager.setStreamSolo(AudioManager.STREAM_BLUETOOTH_SCO, true);
-
broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
}
@@ -2124,13 +2160,6 @@
*/
if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
- //abandon audio focus
- if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
- Log.d(TAG, "abandonAudioFocus");
- // abandon audio focus after the mode has been set back to normal
- mAudioManager.abandonAudioFocusForCall();
- }
Log.d(TAG,"hfp_enable=false");
mAudioManager.setParameters("hfp_enable=false");
broadcastAudioState(mCurrentDevice,
@@ -2197,13 +2226,10 @@
case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
if (mAudioState != BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED) {
mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
- //abandon audio focus for call
- if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
- Log.d(TAG, "abandonAudioFocus");
- // abandon audio focus after the mode has been set back to normal
- mAudioManager.abandonAudioFocusForCall();
- }
+ // Audio focus may still be held by the entity controlling the actual call
+ // (such as Telecom) and hence this will still keep the call around, there
+ // is not much we can do here since dropping the call without user consent
+ // even if the audio connection snapped may not be a good idea.
Log.d(TAG,"hfp_enable=false");
mAudioManager.setParameters("hfp_enable=false");
broadcastAudioState(device,
@@ -2222,8 +2248,6 @@
@Override
public void exit() {
Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
-
- mAudioManager.setStreamSolo(AudioManager.STREAM_BLUETOOTH_SCO, false);
}
}
@@ -2327,7 +2351,6 @@
intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
}
}
-
mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
}
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java
new file mode 100644
index 0000000..c5ebfd0
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.hfpclient.connserv;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.os.Bundle;
+import android.telecom.Conference;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class HfpClientConference extends Conference {
+ private static final String TAG = "HfpClientConference";
+
+ private BluetoothDevice mDevice;
+ private BluetoothHeadsetClient mHeadsetProfile;
+
+ public HfpClientConference(PhoneAccountHandle handle,
+ BluetoothDevice device, BluetoothHeadsetClient client) {
+ super(handle);
+ mDevice = device;
+ mHeadsetProfile = client;
+ boolean manage = HfpClientConnectionService.hasHfpClientEcc(client, device);
+ setConnectionCapabilities(Connection.CAPABILITY_SUPPORT_HOLD |
+ Connection.CAPABILITY_HOLD |
+ (manage ? Connection.CAPABILITY_MANAGE_CONFERENCE : 0));
+ setActive();
+ }
+
+ @Override
+ public void onDisconnect() {
+ Log.d(TAG, "onDisconnect");
+ mHeadsetProfile.terminateCall(mDevice, 0);
+ setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ }
+
+ @Override
+ public void onMerge(Connection connection) {
+ Log.d(TAG, "onMerge " + connection);
+ addConnection(connection);
+ }
+
+ @Override
+ public void onSeparate(Connection connection) {
+ Log.d(TAG, "onSeparate " + connection);
+ ((HfpClientConnection) connection).enterPrivateMode();
+ removeConnection(connection);
+ }
+
+ @Override
+ public void onHold() {
+ Log.d(TAG, "onHold");
+ mHeadsetProfile.holdCall(mDevice);
+ }
+
+ @Override
+ public void onUnhold() {
+ if (getPrimaryConnection().getConnectionService()
+ .getAllConnections().size() > 1) {
+ Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
+ return;
+ }
+ Log.d(TAG, "onUnhold");
+ mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
+ }
+
+ @Override
+ public void onPlayDtmfTone(char c) {
+ Log.d(TAG, "onPlayDtmfTone " + c);
+ if (mHeadsetProfile != null) {
+ mHeadsetProfile.sendDTMF(mDevice, (byte) c);
+ }
+ }
+
+ @Override
+ public void onConnectionAdded(Connection connection) {
+ Log.d(TAG, "onConnectionAdded " + connection);
+ if (connection.getState() == Connection.STATE_HOLDING &&
+ getState() == Connection.STATE_ACTIVE) {
+ connection.onAnswer();
+ } else if (connection.getState() == Connection.STATE_ACTIVE &&
+ getState() == Connection.STATE_HOLDING) {
+ mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
new file mode 100644
index 0000000..4e4e1be
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.hfpclient.connserv;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+public class HfpClientConnection extends Connection {
+ private static final String TAG = "HfpClientConnection";
+
+ private final Context mContext;
+ private final BluetoothDevice mDevice;
+
+ private BluetoothHeadsetClient mHeadsetProfile;
+ private BluetoothHeadsetClientCall mCurrentCall;
+ private boolean mClosed;
+ private boolean mLocalDisconnect;
+ private boolean mClientHasEcc;
+ private boolean mAdded;
+
+ public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
+ BluetoothHeadsetClientCall call, Uri number) {
+ mDevice = device;
+ mContext = context;
+ mHeadsetProfile = client;
+ mCurrentCall = call;
+ if (mHeadsetProfile != null) {
+ mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
+ }
+ setAudioModeIsVoip(false);
+ setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
+ setInitialized();
+
+ if (mHeadsetProfile != null) {
+ finishInitializing();
+ }
+ }
+
+ public void onHfpConnected(BluetoothHeadsetClient client) {
+ mHeadsetProfile = client;
+ mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
+ finishInitializing();
+ }
+
+ public void onHfpDisconnected() {
+ mHeadsetProfile = null;
+ close(DisconnectCause.ERROR);
+ }
+
+ public void onAdded() {
+ mAdded = true;
+ }
+
+ public BluetoothHeadsetClientCall getCall() {
+ return mCurrentCall;
+ }
+
+ public boolean inConference() {
+ return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
+ getState() != Connection.STATE_DISCONNECTED;
+ }
+
+ public void enterPrivateMode() {
+ mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
+ setActive();
+ }
+
+ public void handleCallChanged(BluetoothHeadsetClientCall call) {
+ HfpClientConference conference = (HfpClientConference) getConference();
+ mCurrentCall = call;
+
+ int state = call.getState();
+ Log.d(TAG, "Got call state change to " + state);
+ switch (state) {
+ case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
+ setActive();
+ if (conference != null) {
+ conference.setActive();
+ }
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD:
+ setOnHold();
+ if (conference != null) {
+ conference.setOnHold();
+ }
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
+ case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
+ setDialing();
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
+ case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
+ setRinging();
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
+ // TODO Use more specific causes
+ close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
+ break;
+ default:
+ Log.wtf(TAG, "Unexpected phone state " + state);
+ }
+ setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
+ CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
+ (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
+ }
+
+ private void finishInitializing() {
+ if (mCurrentCall == null) {
+ String number = getAddress().getSchemeSpecificPart();
+ Log.d(TAG, "Dialing " + number);
+ mHeadsetProfile.dial(mDevice, number);
+ setDialing();
+ // We will change state dependent on broadcasts from BluetoothHeadsetClientCall.
+ } else {
+ handleCallChanged(mCurrentCall);
+ }
+ }
+
+ private void close(int cause) {
+ Log.d(TAG, "Closing " + mClosed);
+ if (mClosed) {
+ return;
+ }
+ setDisconnected(new DisconnectCause(cause));
+
+ mClosed = true;
+ mCurrentCall = null;
+
+ destroy();
+ }
+
+ @Override
+ public void onPlayDtmfTone(char c) {
+ Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
+ if (!mClosed && mHeadsetProfile != null) {
+ mHeadsetProfile.sendDTMF(mDevice, (byte) c);
+ }
+ }
+
+ @Override
+ public void onDisconnect() {
+ Log.d(TAG, "onDisconnect " + mCurrentCall);
+ if (!mClosed) {
+ if (mHeadsetProfile != null && mCurrentCall != null) {
+ mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
+ mLocalDisconnect = true;
+ } else {
+ close(DisconnectCause.LOCAL);
+ }
+ }
+ }
+
+ @Override
+ public void onAbort() {
+ Log.d(TAG, "onAbort " + mCurrentCall);
+ onDisconnect();
+ }
+
+ @Override
+ public void onHold() {
+ Log.d(TAG, "onHold " + mCurrentCall);
+ if (!mClosed && mHeadsetProfile != null) {
+ mHeadsetProfile.holdCall(mDevice);
+ }
+ }
+
+ @Override
+ public void onUnhold() {
+ if (getConnectionService().getAllConnections().size() > 1) {
+ Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
+ return;
+ }
+ Log.d(TAG, "onUnhold " + mCurrentCall);
+ if (!mClosed && mHeadsetProfile != null) {
+ mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
+ }
+ }
+
+ @Override
+ public void onAnswer() {
+ Log.d(TAG, "onAnswer " + mCurrentCall);
+ if (!mClosed) {
+ mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
+ }
+ }
+
+ @Override
+ public void onReject() {
+ Log.d(TAG, "onReject " + mCurrentCall);
+ if (!mClosed) {
+ mHeadsetProfile.rejectCall(mDevice);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof HfpClientConnection)) {
+ return false;
+ }
+ Uri otherAddr = ((HfpClientConnection) o).getAddress();
+ return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
+ }
+
+ @Override
+ public int hashCode() {
+ return getAddress() == null ? 0 : getAddress().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
+ mCurrentCall + "}";
+ }
+}
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
new file mode 100644
index 0000000..55ddf22
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.hfpclient.connserv;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HfpClientConnectionService extends ConnectionService {
+ private static final String TAG = "HfpClientConnService";
+
+ public static final String HFP_SCHEME = "hfpc";
+
+ private BluetoothAdapter mAdapter;
+ // Currently active device.
+ private BluetoothDevice mDevice;
+ // Phone account associated with the above device.
+ private PhoneAccount mDevicePhoneAccount;
+ // BluetoothHeadset proxy.
+ private BluetoothHeadsetClient mHeadsetProfile;
+ private TelecomManager mTelecomManager;
+
+ private Map<Uri, HfpClientConnection> mConnections = new HashMap<>();
+ private HfpClientConference mConference;
+
+ private boolean mPendingAcceptCall;
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive " + intent);
+ String action = intent != null ? intent.getAction() : null;
+
+ if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ Log.d(TAG, "Established connection with " + device);
+ synchronized (HfpClientConnectionService.this) {
+ if (device.equals(mDevice)) {
+ // We are already connected and this message can be safeuly ignored.
+ Log.w(TAG, "Got connected for previously connected device, ignoring.");
+ } else {
+ // Since we are connected to a new device close down the previous
+ // account and register the new one.
+ if (mDevicePhoneAccount != null) {
+ mTelecomManager.unregisterPhoneAccount(
+ mDevicePhoneAccount.getAccountHandle());
+ }
+ // Reset the device and the phone account associated.
+ mDevice = device;
+ mDevicePhoneAccount =
+ getAccount(HfpClientConnectionService.this, device);
+ mTelecomManager.registerPhoneAccount(mDevicePhoneAccount);
+ mTelecomManager.enablePhoneAccount(
+ mDevicePhoneAccount.getAccountHandle(), true);
+ mTelecomManager.setUserSelectedOutgoingPhoneAccount(
+ mDevicePhoneAccount.getAccountHandle());
+ }
+ }
+
+ // Add any existing calls to the telecom stack.
+ if (mHeadsetProfile != null) {
+ List<BluetoothHeadsetClientCall> calls =
+ mHeadsetProfile.getCurrentCalls(mDevice);
+ Log.d(TAG, "Got calls " + calls);
+ for (BluetoothHeadsetClientCall call : calls) {
+ handleCall(call);
+ }
+ } else {
+ }
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.d(TAG, "Disconnecting from " + device);
+ // Disconnect any inflight calls from the connection service.
+ synchronized (HfpClientConnectionService.this) {
+ if (device.equals(mDevice)) {
+ Log.d(TAG, "Resetting state for " + device);
+ mDevice = null;
+ disconnectAll();
+ mTelecomManager.unregisterPhoneAccount(
+ mDevicePhoneAccount.getAccountHandle());
+ mDevicePhoneAccount = null;
+ }
+ }
+ }
+ } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) {
+ // If we are not connected, then when we actually do get connected -- the calls should
+ // be added (see ACTION_CONNECTION_STATE_CHANGED intent above).
+ handleCall((BluetoothHeadsetClientCall)
+ intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL));
+ Log.d(TAG, mConnections.size() + " remaining");
+ }
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "onCreate");
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+ mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called");
+ // Close the profile.
+ if (mHeadsetProfile != null) {
+ mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mHeadsetProfile);
+ }
+
+ // Unregister the broadcast receiver.
+ try {
+ unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "Receiver was not registered.");
+ }
+
+ // Unregister the phone account. This should ideally happen when disconnection ensues but in
+ // case the service crashes we may need to force clean.
+ synchronized (this) {
+ mDevice = null;
+ if (mDevicePhoneAccount != null) {
+ mTelecomManager.unregisterPhoneAccount(mDevicePhoneAccount.getAccountHandle());
+ mDevicePhoneAccount = null;
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand " + intent);
+ // In order to make sure that the service is sticky (recovers from errors when HFP
+ // connection is still active) and to stop it we need a special intent since stopService
+ // only recreates it.
+ if (intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, false)) {
+ // Stop the service.
+ stopSelf();
+ return 0;
+ } else {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
+ registerReceiver(mBroadcastReceiver, filter);
+ return START_STICKY;
+ }
+ }
+
+ private void handleCall(BluetoothHeadsetClientCall call) {
+ Log.d(TAG, "Got call " + call);
+
+ Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
+ HfpClientConnection connection = mConnections.get(number);
+ if (connection != null) {
+ connection.handleCallChanged(call);
+ }
+
+ if (connection == null) {
+ // Create the connection here, trigger Telecom to bind to us.
+ buildConnection(call.getDevice(), call, number);
+
+ PhoneAccountHandle handle = getHandle();
+ TelecomManager manager =
+ (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+
+ // Depending on where this call originated make it an incoming call or outgoing
+ // (represented as unknown call in telecom since). Since BluetoothHeadsetClientCall is a
+ // parcelable we simply pack the entire object in there.
+ Bundle b = new Bundle();
+ if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_DIALING ||
+ call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ALERTING ||
+ call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+ // This is an outgoing call. Even if it is an active call we do not have a way of
+ // putting that parcelable in a seaprate field.
+ b.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call);
+ manager.addNewUnknownCall(handle, b);
+ } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_INCOMING) {
+ // This is an incoming call.
+ b.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, call);
+ manager.addNewIncomingCall(handle, b);
+ }
+ } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
+ Log.d(TAG, "Removing number " + number);
+ mConnections.remove(number);
+ }
+ updateConferenceableConnections();
+ }
+
+ // This method is called whenever there is a new incoming call (or right after BT connection).
+ @Override
+ public Connection onCreateIncomingConnection(
+ PhoneAccountHandle connectionManagerAccount,
+ ConnectionRequest request) {
+ Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
+ if (connectionManagerAccount != null &&
+ !getHandle().equals(connectionManagerAccount)) {
+ Log.w(TAG, "HfpClient does not support having a connection manager");
+ return null;
+ }
+
+ // We should already have a connection by this time.
+ BluetoothHeadsetClientCall call =
+ request.getExtras().getParcelable(
+ TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
+ Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
+ HfpClientConnection connection = mConnections.get(number);
+
+ if (connection != null) {
+ connection.onAdded();
+ updateConferenceableConnections();
+ return connection;
+ } else {
+ Log.e(TAG, "Connection should exist in our db, if it doesn't we dont know how to " +
+ "handle this call.");
+ return null;
+ }
+ }
+
+ // This method is called *only if* Dialer UI is used to place an outgoing call.
+ @Override
+ public Connection onCreateOutgoingConnection(
+ PhoneAccountHandle connectionManagerAccount,
+ ConnectionRequest request) {
+ Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
+ if (connectionManagerAccount != null &&
+ !getHandle().equals(connectionManagerAccount)) {
+ Log.w(TAG, "HfpClient does not support having a connection manager");
+ return null;
+ }
+
+ HfpClientConnection connection =
+ buildConnection(getDevice(request.getAccountHandle()), null, request.getAddress());
+ connection.onAdded();
+ return connection;
+ }
+
+ // This method is called when:
+ // 1. Outgoing call created from the AG.
+ // 2. Call transfer from AG -> HF (on connection when existed call present).
+ @Override
+ public Connection onCreateUnknownConnection(
+ PhoneAccountHandle connectionManagerAccount,
+ ConnectionRequest request) {
+ Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
+ if (connectionManagerAccount != null &&
+ !getHandle().equals(connectionManagerAccount)) {
+ Log.w(TAG, "HfpClient does not support having a connection manager");
+ return null;
+ }
+
+ // We should already have a connection by this time.
+ BluetoothHeadsetClientCall call =
+ request.getExtras().getParcelable(
+ TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
+ Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
+ HfpClientConnection connection = mConnections.get(number);
+
+ if (connection != null) {
+ connection.onAdded();
+ updateConferenceableConnections();
+ return connection;
+ } else {
+ Log.e(TAG, "Connection should exist in our db, if it doesn't we dont know how to " +
+ "handle this call " + call);
+ return null;
+ }
+ }
+
+ @Override
+ public void onConference(Connection connection1, Connection connection2) {
+ Log.d(TAG, "onConference " + connection1 + " " + connection2);
+ if (mConference == null) {
+ BluetoothDevice device = getDevice(getHandle());
+ mConference = new HfpClientConference(getHandle(), device, mHeadsetProfile);
+ addConference(mConference);
+ }
+ mConference.setActive();
+ if (connection1.getConference() == null) {
+ mConference.addConnection(connection1);
+ }
+ if (connection2.getConference() == null) {
+ mConference.addConnection(connection2);
+ }
+ }
+
+ private void updateConferenceableConnections() {
+ Collection<HfpClientConnection> all = mConnections.values();
+
+ List<Connection> held = new ArrayList<>();
+ List<Connection> active = new ArrayList<>();
+ List<Connection> group = new ArrayList<>();
+ for (HfpClientConnection connection : all) {
+ switch (connection.getState()) {
+ case Connection.STATE_ACTIVE:
+ active.add(connection);
+ break;
+ case Connection.STATE_HOLDING:
+ held.add(connection);
+ break;
+ default:
+ break;
+ }
+ if (connection.inConference()) {
+ group.add(connection);
+ }
+ }
+ for (Connection connection : held) {
+ connection.setConferenceableConnections(active);
+ }
+ for (Connection connection : active) {
+ connection.setConferenceableConnections(held);
+ }
+ if (group.size() > 1 && mConference == null) {
+ BluetoothDevice device = getDevice(getHandle());
+ mConference = new HfpClientConference(getHandle(), device, mHeadsetProfile);
+ if (group.get(0).getState() == Connection.STATE_ACTIVE) {
+ mConference.setActive();
+ } else {
+ mConference.setOnHold();
+ }
+ for (Connection connection : group) {
+ mConference.addConnection(connection);
+ }
+ addConference(mConference);
+ }
+ if (mConference != null) {
+ List<Connection> toRemove = new ArrayList<>();
+ for (Connection connection : mConference.getConnections()) {
+ if (!((HfpClientConnection) connection).inConference()) {
+ toRemove.add(connection);
+ }
+ }
+ for (Connection connection : toRemove) {
+ mConference.removeConnection(connection);
+ }
+ if (mConference.getConnections().size() <= 1) {
+ mConference.destroy();
+ mConference = null;
+ } else {
+ List<Connection> notConferenced = new ArrayList<>();
+ for (Connection connection : all) {
+ if (connection.getConference() == null &&
+ (connection.getState() == Connection.STATE_HOLDING ||
+ connection.getState() == Connection.STATE_ACTIVE)) {
+ if (((HfpClientConnection) connection).inConference()) {
+ mConference.addConnection(connection);
+ } else {
+ notConferenced.add(connection);
+ }
+ }
+ }
+ mConference.setConferenceableConnections(notConferenced);
+ }
+ }
+ }
+
+ private void disconnectAll() {
+ for (HfpClientConnection connection : mConnections.values()) {
+ connection.onHfpDisconnected();
+ }
+ if (mConference != null) {
+ mConference.destroy();
+ mConference = null;
+ }
+ }
+
+ private BluetoothDevice getDevice(PhoneAccountHandle handle) {
+ PhoneAccount account = mTelecomManager.getPhoneAccount(handle);
+ String btAddr = account.getAddress().getSchemeSpecificPart();
+ return mAdapter.getRemoteDevice(btAddr);
+ }
+
+ private HfpClientConnection buildConnection(
+ BluetoothDevice device, BluetoothHeadsetClientCall call, Uri number) {
+ Log.d(TAG, "Creating connection on " + device + " for " + call + "/" + number);
+ HfpClientConnection connection =
+ new HfpClientConnection(this, device, mHeadsetProfile, call, number);
+ mConnections.put(number, connection);
+ return connection;
+ }
+
+ BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ Log.d(TAG, "onServiceConnected");
+ mHeadsetProfile = (BluetoothHeadsetClient) proxy;
+
+ List<BluetoothDevice> devices = mHeadsetProfile.getConnectedDevices();
+ if (devices == null || devices.size() != 1) {
+ Log.w(TAG, "No connected or more than one connected devices found." + devices);
+ } else { // We have exactly one device connected.
+ Log.d(TAG, "Creating phone account.");
+ synchronized (HfpClientConnectionService.this) {
+ mDevice = devices.get(0);
+ mDevicePhoneAccount = getAccount(HfpClientConnectionService.this, mDevice);
+ mTelecomManager.registerPhoneAccount(mDevicePhoneAccount);
+ mTelecomManager.enablePhoneAccount(
+ mDevicePhoneAccount.getAccountHandle(), true);
+ mTelecomManager.setUserSelectedOutgoingPhoneAccount(
+ mDevicePhoneAccount.getAccountHandle());
+ }
+ }
+
+ for (HfpClientConnection connection : mConnections.values()) {
+ connection.onHfpConnected(mHeadsetProfile);
+ }
+
+ List<BluetoothHeadsetClientCall> calls = mHeadsetProfile.getCurrentCalls(mDevice);
+ Log.d(TAG, "Got calls " + calls);
+ if (calls != null) {
+ for (BluetoothHeadsetClientCall call : calls) {
+ handleCall(call);
+ }
+ }
+
+ if (mPendingAcceptCall) {
+ mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
+ mPendingAcceptCall = false;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ Log.d(TAG, "onServiceDisconnected " + profile);
+ mHeadsetProfile = null;
+ disconnectAll();
+ }
+ };
+
+ public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) {
+ Bundle features = client.getCurrentAgEvents(device);
+ return features == null ? false :
+ features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, false);
+ }
+
+ public synchronized PhoneAccountHandle getHandle() {
+ if (mDevicePhoneAccount == null) throw new IllegalStateException("Handle null??");
+ return mDevicePhoneAccount.getAccountHandle();
+ }
+
+ public static PhoneAccount getAccount(Context context, BluetoothDevice device) {
+ Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
+ PhoneAccountHandle handle = new PhoneAccountHandle(
+ new ComponentName(context, HfpClientConnectionService.class), device.getAddress());
+ PhoneAccount account =
+ new PhoneAccount.Builder(handle, "HFP")
+ .setAddress(addr)
+ .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .build();
+ Log.d(TAG, "phoneaccount: " + account);
+ return account;
+ }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
index 5e14387..4fa2599 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -183,7 +183,8 @@
" - returning empty account list" );
return children;
} finally {
- mProviderClient.release();
+ if (mProviderClient != null)
+ mProviderClient.release();
}
if (c != null) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
index 4b3ecc1..6363415 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -55,6 +55,7 @@
private PackageManager mPackageManager = null;
BluetoothMapAccountLoader mLoader;
BluetoothMapService mMapService = null;
+ private boolean mRegisteredReceiver = false;
public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
mContext = context;
@@ -91,8 +92,12 @@
ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
ArrayList<BluetoothMapAccountItem> addedAccountList =
(ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
- ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
// Same as oldAccountList.clone
+ ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
+ if (oldAccountList == null)
+ oldAccountList = new ArrayList <BluetoothMapAccountItem>();
+ if (removedAccountList == null)
+ removedAccountList = new ArrayList <BluetoothMapAccountItem>();
mFullList.put(app, newAccountList);
for(BluetoothMapAccountItem newAcc: newAccountList){
@@ -155,7 +160,7 @@
public void registerObserver(BluetoothMapAccountItem app) {
Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
- ContentObserver observer = new ContentObserver(new Handler()) {
+ ContentObserver observer = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
@@ -174,7 +179,8 @@
}
};
mObserverMap.put(uri.toString(), observer);
- mResolver.registerContentObserver(uri, true, observer);
+ //False "notifyForDescendents" : Get notified whenever a change occurs to the exact URI.
+ mResolver.registerContentObserver(uri, false, observer);
}
public void unregisterObserver(BluetoothMapAccountItem app) {
@@ -279,12 +285,26 @@
}
}
};
- mContext.registerReceiver(mReceiver,intentFilter);
+ if (!mRegisteredReceiver) {
+ try {
+ mContext.registerReceiver(mReceiver,intentFilter);
+ mRegisteredReceiver = true;
+ } catch (Exception e) {
+ Log.e(TAG,"Unable to register MapAppObserver receiver", e);
+ }
+ }
}
private void removeReceiver(){
if(D)Log.d(TAG,"removeReceiver()\n");
- mContext.unregisterReceiver(mReceiver);
+ if (mRegisteredReceiver) {
+ try {
+ mRegisteredReceiver = false;
+ mContext.unregisterReceiver(mReceiver);
+ } catch (Exception e) {
+ Log.e(TAG,"Unable to unregister mapAppObserver receiver", e);
+ }
+ }
}
/**
@@ -295,12 +315,20 @@
public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
if(D)Log.d(TAG,"getEnabledAccountItems()\n");
ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
- for(BluetoothMapAccountItem app:mFullList.keySet()){
- ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
- for(BluetoothMapAccountItem acc: accountList){
- if(acc.mIsChecked) {
- list.add(acc);
+ for (BluetoothMapAccountItem app:mFullList.keySet()){
+ if (app != null) {
+ ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+ if (accountList != null) {
+ for (BluetoothMapAccountItem acc: accountList) {
+ if (acc.mIsChecked) {
+ list.add(acc);
+ }
+ }
+ } else {
+ Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n");
}
+ } else {
+ Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n");
}
}
return list;
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 7ced91b..d14666f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -139,6 +139,15 @@
public static final int MMS_BCC = 0x81;
public static final int MMS_CC = 0x82;
+ /* OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type.
+ Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130)
+ are interested by user */
+ private static final String INTERESTED_MESSAGE_TYPE_CLAUSE = String
+ .format("( %s = %d OR %s = %d OR %s = %d )", Mms.MESSAGE_TYPE,
+ PduHeaders.MESSAGE_TYPE_SEND_REQ, Mms.MESSAGE_TYPE,
+ PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF, Mms.MESSAGE_TYPE,
+ PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND );
+
public static final String INSERT_ADDRES_TOKEN = "insert-address-token";
private final Context mContext;
@@ -1240,6 +1249,8 @@
}
if (subject != null && subject.length() > subLength) {
subject = subject.substring(0, subLength);
+ } else if (subject == null ) {
+ subject = "";
}
if (V) Log.d(TAG, "setSubject: " + subject);
e.setSubject(subject);
@@ -2119,6 +2130,7 @@
}
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folderElement, fi, ap);
+ where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
if(!where.isEmpty()) {
if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
mmsCursor = mResolver.query(Mms.CONTENT_URI,
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 9aae8cf..ab91ac7 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -48,7 +48,6 @@
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
-import android.util.EventLog;
import android.util.Log;
import android.util.Xml;
import android.text.TextUtils;
@@ -327,6 +326,26 @@
}
}
+ public int getObserverRemoteFeatureMask() {
+ if (V) Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion
+ + " mMapSupportedFeatures: " + mMapSupportedFeatures);
+ return mMapSupportedFeatures;
+ }
+
+ public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
+ mMapSupportedFeatures = remoteSupportedFeatures;
+ if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
+ & mMapSupportedFeatures) != 0) {
+ mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ }
+ // Make sure support for all formats result in latest version returned
+ if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
+ & mMapSupportedFeatures) != 0) {
+ mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+ }
+ if (V) Log.d(TAG, "setObserverRemoteFeatureMask : " + mMapEventReportVersion
+ + " mMapSupportedFeatures : " + mMapSupportedFeatures);
+ }
private Map<Long, Msg> getMsgListSms() {
return mMsgListSms;
@@ -449,8 +468,7 @@
return smsType;
}
- private final ContentObserver mObserver = new ContentObserver(
- new Handler(Looper.getMainLooper())) {
+ private final ContentObserver mObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
@@ -865,28 +883,48 @@
public int setNotificationRegistration(int notificationStatus) throws RemoteException {
// Forward the request to the MNS thread as a message - including the MAS instance ID.
if(D) Log.d(TAG,"setNotificationRegistration() enter");
+ if (mMnsClient == null) {
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
Handler mns = mMnsClient.getMessageHandler();
- if(mns != null) {
+ if (mns != null) {
Message msg = mns.obtainMessage();
- msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
+ if (mMnsClient.isValidMnsRecord()) {
+ msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
+ } else {
+ //Trigger SDP Search and notificaiton registration , if SDP record not found.
+ msg.what = BluetoothMnsObexClient.MSG_MNS_SDP_SEARCH_REGISTRATION;
+ if (mMnsClient.mMnsLstRegRqst != null &&
+ (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) {
+ /* 1. Disallow next Notification ON Request :
+ * - Respond "Service Unavailable" as SDP Search and last notification
+ * registration ON request is already InProgress.
+ * - Next notification ON Request will be allowed ONLY after search
+ * and connect for last saved request [Replied with OK ] is processed.
+ */
+ if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ } else {
+ /* 2. Allow next Notification OFF Request:
+ * - Keep the SDP search still in progress.
+ * - Disconnect and Deregister the contentObserver.
+ */
+ msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
+ }
+ }
+ }
msg.arg1 = mMasId;
msg.arg2 = notificationStatus;
mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
/* Some devices - e.g. PTS needs to get the unregister confirm before we actually
* disconnect the MNS. */
- if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION " +
- "send to MNS");
+ if(D) Log.d(TAG,"setNotificationRegistration() send : " + msg.what + " to MNS ");
+ return ResponseCodes.OBEX_HTTP_OK;
} else {
// This should not happen except at shutdown.
if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
- if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
- registerObserver();
- } else {
- unregisterObserver();
- }
- return ResponseCodes.OBEX_HTTP_OK;
}
boolean eventMaskContainsContacts(long mask) {
@@ -3231,7 +3269,6 @@
(context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS")
!= PackageManager.PERMISSION_GRANTED)) {
Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages");
- EventLog.writeEvent(0x534e4554, "b/22343270", Binder.getCallingUid(), "");
return;
}
@@ -3361,7 +3398,9 @@
};
public void init() {
- mSmsBroadcastReceiver.register();
+ if (mSmsBroadcastReceiver != null) {
+ mSmsBroadcastReceiver.register();
+ }
registerPhoneServiceStateListener();
mInitialized = true;
}
@@ -3369,7 +3408,9 @@
public void deinit() {
mInitialized = false;
unregisterObserver();
- mSmsBroadcastReceiver.unregister();
+ if (mSmsBroadcastReceiver != null) {
+ mSmsBroadcastReceiver.unregister();
+ }
unRegisterPhoneServiceStateListener();
failPendingMessages();
removeDeletedMessages();
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 66e6d3b..0dda44b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -130,6 +130,17 @@
init();
}
+ private void removeSdpRecord() {
+ if (mAdapter != null && mSdpHandle >= 0 &&
+ SdpManager.getDefaultManager() != null) {
+ if (V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
+ " Object reference: " + this + "SDP handle: " + mSdpHandle);
+ boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
+ Log.d(TAG, "RemoveSDPrecord returns " + status);
+ mSdpHandle = -1;
+ }
+ }
+
/* Needed only for test */
protected BluetoothMapMasInstance() {
TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
@@ -277,11 +288,7 @@
Log.e(TAG, "Failed to start the listeners");
return;
}
- if(mSdpHandle >= 0) {
- SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
- if(V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
- " Object reference: " + this + "SDP handle: " + mSdpHandle);
- }
+ removeSdpRecord();
mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(),
mServerSockets.getL2capPsm());
// Here we might have changed crucial data, hence reset DB identifier
@@ -399,6 +406,8 @@
mObserver = null;
}
+ removeSdpRecord();
+
closeConnectionSocket();
closeServerSockets(true);
@@ -434,8 +443,13 @@
}
}
- public void setRemoteFeatureMask(int supported_features) {
- mRemoteFeatureMask = supported_features;
+ public void setRemoteFeatureMask(int supportedFeatures) {
+ if(V) Log.v(TAG, "setRemoteFeatureMask : Curr: "+ mRemoteFeatureMask);
+ mRemoteFeatureMask = supportedFeatures;
+ if (mObserver != null) {
+ mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
+ if(V) Log.v(TAG, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
+ }
}
public int getRemoteFeatureMask(){
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
old mode 100755
new mode 100644
index f696bfa..c44ce33
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -31,6 +31,8 @@
import android.content.IntentFilter.MalformedMimeTypeException;
import android.Manifest;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
@@ -91,6 +93,10 @@
public static final int MSG_RELEASE_WAKE_LOCK = 5006;
+ public static final int MSG_MNS_SDP_SEARCH = 5007;
+
+ public static final int MSG_OBSERVER_REGISTRATION = 5008;
+
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
@@ -138,10 +144,13 @@
private boolean mIsWaitingAuthorization = false;
private boolean mRemoveTimeoutMsg = false;
+ private boolean mRegisteredMapReceiver = false;
private int mPermission = BluetoothDevice.ACCESS_UNKNOWN;
private boolean mAccountChanged = false;
private boolean mSdpSearchInitiated = false;
SdpMnsRecord mMnsRecord = null;
+ private MapServiceMessageHandler mSessionStatusHandler;
+ private boolean mStartError = true;
// package and class name to which we send intent to check phone book access permission
private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
@@ -159,21 +168,18 @@
}
- private final void closeService() {
+ private synchronized void closeService() {
if (DEBUG) Log.d(TAG, "MAP Service closeService in");
if (mBluetoothMnsObexClient != null) {
mBluetoothMnsObexClient.shutdown();
mBluetoothMnsObexClient = null;
}
-
- for(int i=0, c=mMasInstances.size(); i < c; i++) {
- mMasInstances.valueAt(i).shutdown();
- }
- mMasInstances.clear();
-
- if (mSessionStatusHandler != null) {
- mSessionStatusHandler.removeCallbacksAndMessages(null);
+ if (mMasInstances.size() > 0) {
+ for (int i=0, c=mMasInstances.size(); i < c; i++) {
+ mMasInstances.valueAt(i).shutdown();
+ }
+ mMasInstances.clear();
}
mIsWaitingAuthorization = false;
@@ -185,6 +191,19 @@
if (VERBOSE) Log.v(TAG, "CloseService(): Release Wake Lock");
mWakeLock = null;
}
+ /* Only one SHUTDOWN message expected to closeService.
+ * Hence, quit looper and Handler on first SHUTDOWN message*/
+ if (mSessionStatusHandler != null) {
+ //Perform cleanup in Handler running on worker Thread
+ mSessionStatusHandler.removeCallbacksAndMessages(null);
+ Looper looper = mSessionStatusHandler.getLooper();
+ if (looper != null) {
+ looper.quit();
+ if(VERBOSE) Log.i(TAG, "Quit looper");
+ }
+ mSessionStatusHandler = null;
+ if(VERBOSE) Log.i(TAG, "Remove Handler");
+ }
mRemoteDevice = null;
if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
@@ -289,7 +308,7 @@
BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
if(masInst != null) {
masInst.restartObexServerSession();
- } else {
+ } else if(masId == -1) {
for(int i=0, c=mMasInstances.size(); i < c; i++) {
mMasInstances.valueAt(i).restartObexServerSession();
}
@@ -313,7 +332,10 @@
}
}
- private final Handler mSessionStatusHandler = new Handler() {
+ private final class MapServiceMessageHandler extends Handler {
+ private MapServiceMessageHandler(Looper looper) {
+ super(looper);
+ }
@Override
public void handleMessage(Message msg) {
if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
@@ -390,6 +412,30 @@
if (DEBUG) Log.d(TAG, " Released Wake Lock by message");
}
break;
+ case MSG_MNS_SDP_SEARCH:
+ if (mRemoteDevice != null) {
+ if (DEBUG) Log.d(TAG,"MNS SDP Initiate Search ..");
+ mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+ } else {
+ Log.w(TAG, "remoteDevice info not available");
+ }
+ break;
+ case MSG_OBSERVER_REGISTRATION:
+ if (DEBUG) Log.d(TAG,"ContentObserver Registration MASID: " + msg.arg1
+ + " Enable: " + msg.arg2);
+ BluetoothMapMasInstance masInst = mMasInstances.get(msg.arg1);
+ if (masInst != null && masInst.mObserver != null) {
+ try {
+ if (msg.arg2 == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
+ masInst.mObserver.registerObserver();
+ } else {
+ masInst.mObserver.unregisterObserver();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"ContentObserverRegistarion Failed: "+ e);
+ }
+ }
+ break;
default:
break;
}
@@ -425,6 +471,9 @@
return mState;
}
+ protected boolean isMapStarted() {
+ return !mStartError;
+ }
public BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
@@ -464,7 +513,7 @@
public boolean disconnectMap(BluetoothDevice device) {
boolean result = false;
if (DEBUG) Log.d(TAG, "disconnectMap");
- if (getRemoteDevice().equals(device)) {
+ if (getRemoteDevice()!= null && getRemoteDevice().equals(device)) {
switch (mState) {
case BluetoothMap.STATE_CONNECTED:
/* Disconnect all connections and restart all MAS instances */
@@ -542,6 +591,15 @@
@Override
protected boolean start() {
if (DEBUG) Log.d(TAG, "start()");
+ if (isMapStarted()) {
+ Log.w(TAG, "start received for already started, ignoring");
+ return false;
+ }
+ HandlerThread thread = new HandlerThread("BluetoothMapHandler");
+ thread.start();
+ Looper looper = thread.getLooper();
+ mSessionStatusHandler = new MapServiceMessageHandler(looper);
+
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -558,15 +616,17 @@
} catch (MalformedMimeTypeException e) {
Log.e(TAG, "Wrong mime type!!!", e);
}
-
- try {
- registerReceiver(mMapReceiver, filter);
- // We need WRITE_SMS permission to handle messages in
- // actionMessageSentDisconnected()
- registerReceiver(mMapReceiver, filterMessageSent,
- Manifest.permission.WRITE_SMS, null);
- } catch (Exception e) {
- Log.w(TAG,"Unable to register map receiver",e);
+ if (!mRegisteredMapReceiver) {
+ 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);
@@ -577,7 +637,8 @@
// start RFCOMM listener
sendStartListenerMessage(-1);
- return true;
+ mStartError = false;
+ return !mStartError;
}
/**
@@ -738,13 +799,27 @@
@Override
protected boolean stop() {
if (DEBUG) Log.d(TAG, "stop()");
- try {
- unregisterReceiver(mMapReceiver);
- mAppObserver.shutdown();
- } catch (Exception e) {
- Log.w(TAG,"Unable to unregister map receiver",e);
+ if (mRegisteredMapReceiver) {
+ try {
+ mRegisteredMapReceiver = false;
+ unregisterReceiver(mMapReceiver);
+ mAppObserver.shutdown();
+ } catch (Exception e) {
+ Log.e(TAG,"Unable to unregister map receiver",e);
+ }
}
-
+ //Stop MapProfile if already started.
+ //TODO: Check if the profile state can be retreived from ProfileService or AdapterService.
+ if (!isMapStarted()) {
+ if (DEBUG) Log.d(TAG, "Service Not Available to STOP, ignoring");
+ return true;
+ } else {
+ if (VERBOSE) Log.d(TAG, "Service Stoping()");
+ }
+ if (mSessionStatusHandler != null) {
+ sendShutdownMessage();
+ }
+ mStartError = true;
setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
sendShutdownMessage();
return true;
@@ -753,8 +828,9 @@
public boolean cleanup() {
if (DEBUG) Log.d(TAG, "cleanup()");
setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
- // TODO: Change to use message? - do we need to wait for completion?
- closeService();
+ //Cleanup already handled in Stop().
+ //Move this extra check to Handler.
+ sendShutdownMessage();
return true;
}
@@ -835,10 +911,12 @@
private void cancelUserTimeoutAlarm(){
if (DEBUG) Log.d(TAG,"cancelUserTimeOutAlarm()");
- Intent intent = new Intent(this, BluetoothMapService.class);
- PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
+ Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+ PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+ pIntent.cancel();
+
AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
- alarmManager.cancel(sender);
+ alarmManager.cancel(pIntent);
mRemoveTimeoutMsg = false;
}
@@ -847,13 +925,16 @@
* @param masId the MasID to start. Use -1 to start all listeners.
*/
public void sendStartListenerMessage(int masId) {
- if(mSessionStatusHandler != null) {
+ if (mSessionStatusHandler != null && ! mSessionStatusHandler.hasMessages(START_LISTENER)) {
Message msg = mSessionStatusHandler.obtainMessage(START_LISTENER, masId, 0);
/* We add a small delay here to ensure the call returns true before this message is
* handled. It seems wrong to add a delay, but the alternative is to build a lock
* system to handle synchronization, which isn't nice either... */
mSessionStatusHandler.sendMessageDelayed(msg, 20);
- } // Can only be null during shutdown
+ } else if (mSessionStatusHandler != null) {
+ if(DEBUG)
+ Log.w(TAG, "mSessionStatusHandler START_LISTENER message already in Queue");
+ }
}
private void sendConnectMessage(int masId) {
@@ -889,9 +970,23 @@
mIsWaitingAuthorization = false;
cancelUserTimeoutAlarm();
}
- mSessionStatusHandler.removeCallbacksAndMessages(null);
- // Request release of all resources
- mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
+ if (mSessionStatusHandler != null && !mSessionStatusHandler.hasMessages(SHUTDOWN)) {
+ mSessionStatusHandler.removeCallbacksAndMessages(null);
+ // Request release of all resources
+ Message msg = mSessionStatusHandler.obtainMessage(SHUTDOWN);
+ if( mSessionStatusHandler.sendMessage(msg) == false) {
+ /* most likely caused by shutdown being called from multiple sources - e.g.BT off
+ * signaled through intent and a service shutdown simultaneously.
+ * Intended behavior not documented, hence we need to be able to handle all cases*/
+ } else {
+ if(DEBUG)
+ Log.e(TAG, "mSessionStatusHandler.sendMessage() dispatched shutdown message");
+ }
+ } else if (mSessionStatusHandler != null) {
+ if(DEBUG)
+ Log.w(TAG, "mSessionStatusHandler shutdown message already in Queue");
+ }
+ if (VERBOSE) Log.d(TAG, "sendShutdownMessage() Out");
}
private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
@@ -969,31 +1064,34 @@
}
sendConnectCancelMessage();
}
- } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)){
-// Log.v(TAG, "Received ACTION_SDP_RECORD.");
+ } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
+ if (DEBUG) Log.d(TAG, "Received ACTION_SDP_RECORD.");
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
if (VERBOSE) {
Log.v(TAG, "Received UUID: " + uuid.toString());
Log.v(TAG, "expected UUID: " +
BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString());
}
- if(uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)
- && mSdpSearchInitiated)
- {
+ if (uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)) {
mMnsRecord = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
if (VERBOSE) {
Log.v(TAG, " -> MNS Record:" + mMnsRecord);
Log.v(TAG, " -> status: " + status);
}
- mSdpSearchInitiated = false; // done searching
- if(status != -1 && mMnsRecord != null){
- for(int i=0, c=mMasInstances.size(); i < c; i++) {
+ if (mBluetoothMnsObexClient != null && !mSdpSearchInitiated) {
+ mBluetoothMnsObexClient.setMnsRecord(mMnsRecord);
+ }
+ if (status != -1 && mMnsRecord != null) {
+ for (int i = 0, c = mMasInstances.size(); i < c; i++) {
mMasInstances.valueAt(i).setRemoteFeatureMask(
mMnsRecord.getSupportedFeatures());
}
}
- sendConnectMessage(-1); // -1 indicates all MAS instances
+ if (mSdpSearchInitiated) {
+ mSdpSearchInitiated = false; // done searching
+ sendConnectMessage(-1); // -1 indicates all MAS instances
+ }
}
} else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) {
if (VERBOSE) Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 66ceecc..f10c4ec 100755
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -394,15 +394,16 @@
static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
throws UnsupportedEncodingException {
- byte[] utf8Bytes = null;
+ byte[] utf8Bytes = new byte[utf8String.length() + 1];
try {
- utf8Bytes = utf8String.getBytes("UTF-8");
+ System.arraycopy(utf8String.getBytes("UTF-8"), 0,
+ utf8Bytes, 0, utf8String.length());
} catch (UnsupportedEncodingException e) {
Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
throw e;
}
- if (utf8Bytes.length > (maxLength - 1)) {
+ if (utf8Bytes.length > maxLength) {
/* if 'continuation' byte is in place 200,
* then strip previous bytes until utf-8 start byte is found */
if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index 6c12018..2558641 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -60,12 +60,16 @@
private HeaderSet mHsConnect = null;
private Handler mCallback = null;
- private final SdpMnsRecord mMnsRecord;
+ private SdpMnsRecord mMnsRecord;
// Used by the MAS to forward notification registrations
public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
public static final int MSG_MNS_SEND_EVENT = 2;
+ public static final int MSG_MNS_SDP_SEARCH_REGISTRATION = 3;
-
+ //Copy SdpManager.SDP_INTENT_DELAY - The timeout to wait for reply from native.
+ private final int MNS_SDP_SEARCH_DELAY = 6000;
+ public MnsSdpSearchInfo mMnsLstRegRqst = null;
+ private static final int MNS_NOTIFICATION_DELAY = 10;
public static final ParcelUuid BLUETOOTH_UUID_OBEX_MNS =
ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
@@ -90,6 +94,26 @@
return mHandler;
}
+ class MnsSdpSearchInfo {
+ private boolean isSearchInProgress;
+ int lastMasId;
+ int lastNotificationStatus;
+
+ MnsSdpSearchInfo (boolean isSearchON, int masId, int notification) {
+ isSearchInProgress = isSearchON;
+ lastMasId = masId;
+ lastNotificationStatus = notification;
+ }
+
+ public boolean isSearchInProgress() {
+ return isSearchInProgress;
+ }
+
+ public void setIsSearchInProgress(boolean isSearchON) {
+ isSearchInProgress = isSearchON;
+ }
+ }
+
private final class MnsObexClientHandler extends Handler {
private MnsObexClientHandler(Looper looper) {
super(looper);
@@ -99,11 +123,29 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MNS_NOTIFICATION_REGISTRATION:
- handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
+ if (V) Log.v(TAG, "Reg masId: " + msg.arg1 + " notfStatus: " + msg.arg2);
+ if (isValidMnsRecord()) {
+ handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
+ } else {
+ //Should not happen
+ if (D) Log.d(TAG, "MNS SDP info not available yet - Cannot Connect.");
+ }
break;
case MSG_MNS_SEND_EVENT:
sendEventHandler((byte[])msg.obj/*byte[]*/, msg.arg1 /*masId*/);
break;
+ case MSG_MNS_SDP_SEARCH_REGISTRATION:
+ //Initiate SDP Search
+ notifyMnsSdpSearch();
+ //Save the mns search info
+ mMnsLstRegRqst = new MnsSdpSearchInfo(true, msg.arg1, msg.arg2);
+ //Handle notification registration.
+ Message msgReg =
+ mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION,
+ msg.arg1, msg.arg2);
+ if (V) Log.v(TAG, "SearchReg masId: " + msg.arg1 + " notfStatus: " + msg.arg2);
+ mHandler.sendMessageDelayed(msgReg, MNS_SDP_SEARCH_DELAY);
+ break;
default:
break;
}
@@ -153,7 +195,7 @@
/**
* Shutdown the MNS.
*/
- public void shutdown() {
+ public synchronized void shutdown() {
/* should shutdown handler thread first to make sure
* handleRegistration won't be called when disconnect
*/
@@ -178,26 +220,82 @@
* @param masId
* @param notificationStatus
*/
- public void handleRegistration(int masId, int notificationStatus){
+ public synchronized void handleRegistration(int masId, int notificationStatus){
if(D) Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
-
- if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
+ boolean sendObserverRegistration = true;
+ if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
mRegisteredMasIds.delete(masId);
- } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
+ if (mMnsLstRegRqst != null && mMnsLstRegRqst.lastMasId == masId) {
+ //Clear last saved MNSSdpSearchInfo , if Disconnect requested for same MasId.
+ mMnsLstRegRqst = null;
+ }
+ } else if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
/* Connect if we do not have a connection, and start the content observers providing
* this thread as Handler.
*/
- if(isConnected() == false) {
+ if (isConnected() == false) {
if(D) Log.d(TAG, "handleRegistration: connect");
connect();
}
+ sendObserverRegistration = isConnected();
mRegisteredMasIds.put(masId, true); // We don't use the value for anything
+
+ // Clear last saved MNSSdpSearchInfo after connect is processed.
+ mMnsLstRegRqst = null;
}
- if(mRegisteredMasIds.size() == 0) {
+
+ if (mRegisteredMasIds.size() == 0) {
// No more registrations - disconnect
if(D) Log.d(TAG, "handleRegistration: disconnect");
disconnect();
}
+
+ //Register ContentObserver After connect/disconnect MNS channel.
+ if (V) Log.v(TAG, "Send registerObserver: " + sendObserverRegistration);
+ if (mCallback != null && sendObserverRegistration) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothMapService.MSG_OBSERVER_REGISTRATION;
+ msg.arg1 = masId;
+ msg.arg2 = notificationStatus;
+ msg.sendToTarget();
+ }
+ }
+
+ public boolean isValidMnsRecord() {
+ return (mMnsRecord != null);
+ }
+
+ public void setMnsRecord(SdpMnsRecord mnsRecord) {
+ if (V) Log.v(TAG, "setMNSRecord");
+ if (isValidMnsRecord()) {
+ Log.w(TAG,"MNS Record already available. Still update.");
+ }
+ mMnsRecord = mnsRecord;
+ if (mMnsLstRegRqst != null) {
+ //SDP Search completed.
+ mMnsLstRegRqst.setIsSearchInProgress(false);
+ if (mHandler.hasMessages(MSG_MNS_NOTIFICATION_REGISTRATION)) {
+ mHandler.removeMessages(MSG_MNS_NOTIFICATION_REGISTRATION);
+ //Search Result obtained within MNS_SDP_SEARCH_DELAY timeout
+ if (!isValidMnsRecord()) {
+ // SDP info still not available for last trial.
+ // Clear saved info.
+ mMnsLstRegRqst = null;
+ } else {
+ if (V) Log.v(TAG, "Handle registration for last saved request");
+ Message msgReg =
+ mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION);
+ msgReg.arg1 = mMnsLstRegRqst.lastMasId;
+ msgReg.arg2 = mMnsLstRegRqst.lastNotificationStatus;
+ if (V) Log.v(TAG, "SearchReg masId: " + msgReg.arg1
+ + " notfStatus: " + msgReg.arg2);
+ //Handle notification registration.
+ mHandler.sendMessageDelayed(msgReg, MNS_NOTIFICATION_DELAY);
+ }
+ }
+ } else {
+ if (V) Log.v(TAG, "No last saved MNSSDPInfo to handle");
+ }
}
public void connect() {
@@ -207,11 +305,11 @@
BluetoothSocket btSocket = null;
try {
// TODO: Do SDP record search again?
- if(mMnsRecord != null && mMnsRecord.getL2capPsm() > 0) {
+ if (isValidMnsRecord() && mMnsRecord.getL2capPsm() > 0) {
// Do L2CAP connect
btSocket = mRemoteDevice.createL2capSocket(mMnsRecord.getL2capPsm());
- } else if (mMnsRecord != null && mMnsRecord.getRfcommChannelNumber() > 0) {
+ } else if (isValidMnsRecord() && mMnsRecord.getRfcommChannelNumber() > 0) {
// Do Rfcomm connect
btSocket = mRemoteDevice.createRfcommSocket(mMnsRecord.getRfcommChannelNumber());
} else {
@@ -280,6 +378,14 @@
notifyUpdateWakeLock();
}
+ private void notifyMnsSdpSearch() {
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothMapService.MSG_MNS_SDP_SEARCH;
+ msg.sendToTarget();
+ }
+ }
+
private int sendEventHandler(byte[] eventBytes, int masInstanceId) {
boolean error = false;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 39116a0..0d4b5b2 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -123,7 +123,7 @@
} else if (extra_text != null && type != null) {
if (V) Log.v(TAG, "Get ACTION_SEND intent with Extra_text = "
+ extra_text.toString() + "; mimetype = " + type);
- final Uri fileUri = creatFileForSharedContent(this, extra_text);
+ final Uri fileUri = creatFileForSharedContent(this.createCredentialProtectedStorageContext(), extra_text);
if (fileUri != null) {
Thread t = new Thread(new Runnable() {
public void run() {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 32c8b52..da7a0c6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -41,10 +41,12 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.text.format.Formatter;
import android.util.Log;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
+
import java.util.HashMap;
/**
@@ -495,37 +497,42 @@
}
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- CharSequence title =
- mContext.getText(R.string.incoming_file_confirm_Notification_title);
- CharSequence caption = mContext
- .getText(R.string.incoming_file_confirm_Notification_caption);
- int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
- long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
- Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
+ BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
+ BluetoothOppUtility.fillRecord(mContext, cursor, info);
+ Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID);
+ Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
+ .setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
- Notification n = new Notification();
- n.icon = R.drawable.bt_incomming_file_notification;
- n.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
- n.flags |= Notification.FLAG_ONGOING_EVENT;
- n.defaults = Notification.DEFAULT_SOUND;
- n.tickerText = title;
-
- Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);
- intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
- intent.setDataAndNormalize(contentUri);
-
- n.when = timeStamp;
- n.color = mContext.getResources().getColor(
- com.android.internal.R.color.system_notification_accent_color);
- n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,
- intent, 0));
-
- intent = new Intent(Constants.ACTION_HIDE);
- intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
- intent.setDataAndNormalize(contentUri);
- n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
-
- mNotificationMgr.notify(id, n);
+ Notification n = new Notification.Builder(mContext)
+ .setOnlyAlertOnce(true)
+ .setOngoing(true)
+ .setVibrate(new long[] { 200 })
+ .setWhen(info.mTimeStamp)
+ .setDefaults(Notification.DEFAULT_SOUND)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .addAction(R.drawable.ic_decline,
+ mContext.getText(R.string.incoming_file_confirm_cancel),
+ PendingIntent.getBroadcast(mContext, 0,
+ new Intent(baseIntent).setAction(Constants.ACTION_DECLINE), 0))
+ .addAction(R.drawable.ic_accept,
+ mContext.getText(R.string.incoming_file_confirm_ok),
+ PendingIntent.getBroadcast(mContext, 0,
+ new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0))
+ .setContentIntent(PendingIntent.getBroadcast(mContext, 0,
+ new Intent(baseIntent).setAction(Constants.ACTION_INCOMING_FILE_CONFIRM), 0))
+ .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
+ new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0))
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(mContext.getText(R.string.incoming_file_confirm_Notification_title))
+ .setContentText(info.mFileName)
+ .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
+ R.string.incoming_file_confirm_Notification_content,
+ info.mDeviceName, info.mFileName)))
+ .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes))
+ .setSmallIcon(R.drawable.bt_incomming_file_notification)
+ .build();
+ mNotificationMgr.notify(info.mID, n);
}
cursor.close();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index 1a8ac6c..97dc0b3 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -337,7 +337,7 @@
private int sendFile(BluetoothOppSendFileInfo fileInfo) {
boolean error = false;
int responseCode = -1;
- int position = 0;
+ long position = 0;
int status = BluetoothShare.STATUS_SUCCESS;
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
ContentValues updateValues;
@@ -393,6 +393,8 @@
if (!error) {
int readLength = 0;
+ long percent = 0;
+ long prevPercent = 0;
boolean okToProceed = false;
long timestamp = 0;
int outputBufferSize = putOperation.getMaxPacketSize();
@@ -465,10 +467,15 @@
+ " readLength " + readLength + " bytes took "
+ (System.currentTimeMillis() - timestamp) + " ms");
}
- updateValues = new ContentValues();
- updateValues.put(BluetoothShare.CURRENT_BYTES, position);
- mContext1.getContentResolver().update(contentUri, updateValues,
- null, null);
+ // Update the Progress Bar only if there is change in percentage
+ percent = position * 100 / fileInfo.mLength;
+ if (percent > prevPercent) {
+ updateValues = new ContentValues();
+ updateValues.put(BluetoothShare.CURRENT_BYTES, position);
+ mContext1.getContentResolver().update(contentUri, updateValues,
+ null, null);
+ prevPercent = percent;
+ }
}
}
}
@@ -549,6 +556,10 @@
private void handleSendException(String exception) {
Log.e(TAG, "Error when sending file: " + exception);
+ // Update interrupted outbound content resolver entry when
+ // error during transfer.
+ Constants.updateShareStatus(mContext1, mInfo.mId,
+ BluetoothShare.STATUS_OBEX_DATA_ERROR);
mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index ffb00e9..2843249 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -93,8 +93,6 @@
private BluetoothOppReceiveFileInfo mFileInfo;
- private WakeLock mWakeLock;
-
private WakeLock mPartialWakeLock;
boolean mTimeoutMsgSent = false;
@@ -103,8 +101,6 @@
mContext = context;
mTransport = transport;
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
- | PowerManager.ON_AFTER_RELEASE, TAG);
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
@@ -276,43 +272,27 @@
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) {
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 (needConfirm) {
- if (V) Log.d(TAG, "acquire full WakeLock");
- mWakeLock.acquire();
-
- Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION);
- in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
- mContext.sendBroadcast(in);
- }
-
if (V) Log.v(TAG, "insert contentUri: " + contentUri);
if (V) Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
synchronized (this) {
- if (mWakeLock.isHeld()) {
- if (V) Log.v(TAG, "acquire partial WakeLock");
- mPartialWakeLock.acquire();
- mWakeLock.release();
- }
+ mPartialWakeLock.acquire();
mServerBlocking = true;
try {
@@ -454,7 +434,10 @@
mContext.getContentResolver().update(contentUri, updateValues, null, null);
}
- int position = 0;
+ long position = 0;
+ long percent = 0;
+ long prevPercent = 0;
+
if (!error) {
bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000);
}
@@ -478,6 +461,7 @@
bos.write(b, 0, readLength);
position += readLength;
+ percent = position * 100 / fileInfo.mLength;
if (V) {
Log.v(TAG, "Receive file position = " + position + " readLength "
@@ -485,9 +469,13 @@
+ (System.currentTimeMillis() - timestamp) + " ms");
}
- ContentValues updateValues = new ContentValues();
- updateValues.put(BluetoothShare.CURRENT_BYTES, position);
- mContext.getContentResolver().update(contentUri, updateValues, null, null);
+ // Update the Progress Bar only if there is change in percentage
+ if (percent > prevPercent) {
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(BluetoothShare.CURRENT_BYTES, position);
+ mContext.getContentResolver().update(contentUri, updateValues, null, null);
+ prevPercent = percent;
+ }
}
} catch (IOException e1) {
Log.e(TAG, "Error when receiving file");
@@ -588,9 +576,6 @@
}
private synchronized void releaseWakeLocks() {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
if (mPartialWakeLock.isHeld()) {
mPartialWakeLock.release();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index f4c8d9c..f0c3d83 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -118,18 +118,25 @@
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
in.setDataAndNormalize(uri);
context.startActivity(in);
+ cancelNotification(context, uri);
- NotificationManager notMgr = (NotificationManager)context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (notMgr != null) {
- notMgr.cancel((int)ContentUris.parseId(intent.getData()));
- if (V) Log.v(TAG, "notMgr.cancel called");
- }
- } else if (action.equals(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION)) {
- if (V) Log.v(TAG, "Receiver INCOMING_FILE_NOTIFICATION");
+ } else if (action.equals(Constants.ACTION_DECLINE)) {
+ if (V) Log.v(TAG, "Receiver ACTION_DECLINE");
- Toast.makeText(context, context.getString(R.string.incoming_file_toast_msg),
- Toast.LENGTH_SHORT).show();
+ Uri uri = intent.getData();
+ ContentValues values = new ContentValues();
+ values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_DENIED);
+ context.getContentResolver().update(uri, values, null, null);
+ cancelNotification(context, uri);
+
+ } else if (action.equals(Constants.ACTION_ACCEPT)) {
+ if (V) Log.v(TAG, "Receiver ACTION_ACCEPT");
+
+ Uri uri = intent.getData();
+ ContentValues values = new ContentValues();
+ values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_CONFIRMED);
+ context.getContentResolver().update(uri, values, null, null);
+ cancelNotification(context, uri);
} else if (action.equals(Constants.ACTION_OPEN) || action.equals(Constants.ACTION_LIST)) {
if (V) {
@@ -161,12 +168,7 @@
context.startActivity(in);
}
- NotificationManager notMgr = (NotificationManager)context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (notMgr != null) {
- notMgr.cancel((int)ContentUris.parseId(intent.getData()));
- if (V) Log.v(TAG, "notMgr.cancel called");
- }
+ cancelNotification(context, uri);
} else if (action.equals(Constants.ACTION_OPEN_OUTBOUND_TRANSFER)) {
if (V) Log.v(TAG, "Received ACTION_OPEN_OUTBOUND_TRANSFER.");
@@ -279,4 +281,13 @@
}
}
}
+
+ private void cancelNotification(Context context, Uri uri) {
+ NotificationManager notMgr = (NotificationManager)context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notMgr != null) {
+ notMgr.cancel((int)ContentUris.parseId(uri));
+ if (V) Log.v(TAG, "notMgr.cancel called");
+ }
+ }
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index f952bec..a5c8e27 100755
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -578,9 +578,9 @@
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)),
cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)),
- cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
- cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
- cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
+ cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
+ cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
+ cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) != Constants.MEDIA_SCANNED_NOT_SCANNED);
if (V) {
@@ -730,10 +730,10 @@
}
info.mStatus = newStatus;
- info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
- info.mCurrentBytes = cursor.getInt(cursor
+ info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
+ info.mCurrentBytes = cursor.getLong(cursor
.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
- info.mTimestamp = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
+ info.mTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
info.mMediaScanned = (cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) != Constants.MEDIA_SCANNED_NOT_SCANNED);
if (confirmUpdated) {
@@ -868,7 +868,8 @@
mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, nextBatch,
mServerSession);
mServerTransfer.start();
- if (nextBatch.getPendingShare().mConfirm ==
+ if (nextBatch.getPendingShare() != null
+ && nextBatch.getPendingShare().mConfirm ==
BluetoothShare.USER_CONFIRMATION_CONFIRMED) {
mServerTransfer.confirmStatusChanged();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java b/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
index 32f6b3c..7fbdd0c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppShareInfo.java
@@ -60,9 +60,9 @@
public int mStatus;
- public int mTotalBytes;
+ public long mTotalBytes;
- public int mCurrentBytes;
+ public long mCurrentBytes;
public long mTimestamp;
@@ -70,7 +70,7 @@
public BluetoothOppShareInfo(int id, Uri uri, String hint, String filename, String mimetype,
int direction, String destination, int visibility, int confirm, int status,
- int totalBytes, int currentBytes, int timestamp, boolean mediaScanned) {
+ long totalBytes, long currentBytes, long timestamp, boolean mediaScanned) {
mId = id;
mUri = uri;
mHint = hint;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 58f4677..ab4613f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -67,59 +67,11 @@
= new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
- info.mID = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
- info.mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
- info.mDirection = cursor.getInt(cursor
- .getColumnIndexOrThrow(BluetoothShare.DIRECTION));
- info.mTotalBytes = cursor.getLong(cursor
- .getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
- info.mCurrentBytes = cursor.getLong(cursor
- .getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
- info.mTimeStamp = cursor.getLong(cursor
- .getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
- info.mDestAddr = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare.DESTINATION));
-
- info.mFileName = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare._DATA));
- if (info.mFileName == null) {
- info.mFileName = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
- }
- if (info.mFileName == null) {
- info.mFileName = context.getString(R.string.unknown_file);
- }
-
- info.mFileUri = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
-
- if (info.mFileUri != null) {
- Uri u = Uri.parse(info.mFileUri);
- info.mFileType = context.getContentResolver().getType(u);
- } else {
- Uri u = Uri.parse(info.mFileName);
- info.mFileType = context.getContentResolver().getType(u);
- }
- if (info.mFileType == null) {
- info.mFileType = cursor.getString(cursor
- .getColumnIndexOrThrow(BluetoothShare.MIMETYPE));
- }
-
- BluetoothDevice remoteDevice = adapter.getRemoteDevice(info.mDestAddr);
- info.mDeviceName =
- BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
-
- int confirmationType = cursor.getInt(
- cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
- info.mHandoverInitiated =
- confirmationType == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
-
- if (V) Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType
- + info.mDestAddr);
+ fillRecord(context, cursor, info);
}
cursor.close();
} else {
@@ -129,6 +81,58 @@
return info;
}
+ public static void fillRecord(Context context, Cursor cursor, BluetoothOppTransferInfo info) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ info.mID = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
+ info.mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
+ info.mDirection = cursor.getInt(cursor
+ .getColumnIndexOrThrow(BluetoothShare.DIRECTION));
+ info.mTotalBytes = cursor.getLong(cursor
+ .getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
+ info.mCurrentBytes = cursor.getLong(cursor
+ .getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
+ info.mTimeStamp = cursor.getLong(cursor
+ .getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
+ info.mDestAddr = cursor.getString(cursor
+ .getColumnIndexOrThrow(BluetoothShare.DESTINATION));
+
+ info.mFileName = cursor.getString(cursor
+ .getColumnIndexOrThrow(BluetoothShare._DATA));
+ if (info.mFileName == null) {
+ info.mFileName = cursor.getString(cursor
+ .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+ }
+ if (info.mFileName == null) {
+ info.mFileName = context.getString(R.string.unknown_file);
+ }
+
+ info.mFileUri = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
+
+ if (info.mFileUri != null) {
+ Uri u = Uri.parse(info.mFileUri);
+ info.mFileType = context.getContentResolver().getType(u);
+ } else {
+ Uri u = Uri.parse(info.mFileName);
+ info.mFileType = context.getContentResolver().getType(u);
+ }
+ if (info.mFileType == null) {
+ info.mFileType = cursor.getString(cursor
+ .getColumnIndexOrThrow(BluetoothShare.MIMETYPE));
+ }
+
+ BluetoothDevice remoteDevice = adapter.getRemoteDevice(info.mDestAddr);
+ info.mDeviceName =
+ BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
+
+ int confirmationType = cursor.getInt(
+ cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
+ info.mHandoverInitiated =
+ confirmationType == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
+
+ if (V) Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType
+ + info.mDestAddr);
+ }
+
/**
* Organize Array list for transfers in one batch
*/
diff --git a/src/com/android/bluetooth/opp/BluetoothShare.java b/src/com/android/bluetooth/opp/BluetoothShare.java
index d033325..94c1fc2 100644
--- a/src/com/android/bluetooth/opp/BluetoothShare.java
+++ b/src/com/android/bluetooth/opp/BluetoothShare.java
@@ -64,12 +64,6 @@
/**
* This is sent by the Bluetooth Share component to indicate there is an
- * incoming file need user to confirm.
- */
- public static final String INCOMING_FILE_CONFIRMATION_REQUEST_ACTION = "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION";
-
- /**
- * This is sent by the Bluetooth Share component to indicate there is an
* incoming file request timeout and need update UI.
*/
public static final String USER_CONFIRMATION_TIMEOUT_ACTION = "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT";
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index e15f13e..acabb49 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -153,12 +153,21 @@
"com.android.intent.extra.CONNECTION_HANDOVER";
/**
- * the intent that gets sent when deleting the incoming file confirmation
- * notification
+ * the intent that gets sent when deleting the incoming file confirmation notification
*/
public static final String ACTION_HIDE = "android.btopp.intent.action.HIDE";
/**
+ * the intent that gets sent when accepting the incoming file confirmation notification
+ */
+ public static final String ACTION_ACCEPT = "android.btopp.intent.action.ACCEPT";
+
+ /**
+ * the intent that gets sent when declining the incoming file confirmation notification
+ */
+ public static final String ACTION_DECLINE = "android.btopp.intent.action.DECLINE";
+
+ /**
* the intent that gets sent when deleting the notifications of outbound and
* inbound completed transfer
*/
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index 9437794..22b3bc6 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -27,7 +27,8 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
-import android.net.NetworkUtils;
+import android.net.ip.IpManager;
+import android.net.ip.IpManager.WaitForProvisioningCallback;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
@@ -56,7 +57,8 @@
// All accesses to these must be synchronized(this).
private final NetworkInfo mNetworkInfo;
- private LinkProperties mLinkProperties;
+ private IpManager mIpManager;
+ private String mInterfaceName;
private NetworkAgent mNetworkAgent;
public BluetoothTetheringNetworkFactory(Context context, Looper looper, PanService panService) {
@@ -66,54 +68,72 @@
mPanService = panService;
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORK_TYPE, "");
- mLinkProperties = new LinkProperties();
mNetworkCapabilities = new NetworkCapabilities();
initNetworkCapabilities();
setCapabilityFilter(mNetworkCapabilities);
}
+ private void stopIpManagerLocked() {
+ if (mIpManager != null) {
+ mIpManager.shutdown();
+ mIpManager = null;
+ }
+ }
+
// Called by NetworkFactory when PanService and NetworkFactory both desire a Bluetooth
// reverse-tether connection. A network interface for Bluetooth reverse-tethering can be
// assumed to be available because we only register our NetworkFactory when it is so.
@Override
protected void startNetwork() {
- // TODO: Handle DHCP renew.
- Thread dhcpThread = new Thread(new Runnable() {
+ // TODO: Figure out how to replace this thread with simple invocations
+ // of IpManager. This will likely necessitate a rethink about
+ // NetworkAgent, NetworkInfo, and associated instance lifetimes.
+ Thread ipProvisioningThread = new Thread(new Runnable() {
public void run() {
LinkProperties linkProperties;
+ final WaitForProvisioningCallback ipmCallback = new WaitForProvisioningCallback() {
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ synchronized (BluetoothTetheringNetworkFactory.this) {
+ if (mNetworkAgent != null && mNetworkInfo.isConnected()) {
+ mNetworkAgent.sendLinkProperties(newLp);
+ }
+ }
+ }
+ };
+
synchronized (BluetoothTetheringNetworkFactory.this) {
- linkProperties = mLinkProperties;
- if (linkProperties.getInterfaceName() == null) {
+ if (TextUtils.isEmpty(mInterfaceName)) {
Slog.e(TAG, "attempted to reverse tether without interface name");
return;
}
- log("dhcpThread(+" + linkProperties.getInterfaceName() +
- "): mNetworkInfo=" + mNetworkInfo);
+ log("ipProvisioningThread(+" + mInterfaceName + "): " +
+ "mNetworkInfo=" + mNetworkInfo);
+ mIpManager = new IpManager(mContext, mInterfaceName, ipmCallback);
+ mIpManager.startProvisioning(
+ mIpManager.buildProvisioningConfiguration()
+ .withoutIpReachabilityMonitor()
+ .build());
+ mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, null);
}
- DhcpResults dhcpResults = new DhcpResults();
- // TODO: Handle DHCP renewals better.
- // In general runDhcp handles DHCP renewals for us, because
- // the dhcp client stays running, but if the renewal fails,
- // we will lose our IP address and connectivity without
- // noticing.
- if (!NetworkUtils.runDhcp(linkProperties.getInterfaceName(), dhcpResults)) {
- Slog.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
+ linkProperties = ipmCallback.waitForProvisioning();
+ if (linkProperties == null) {
+ Slog.e(TAG, "IP provisioning error.");
synchronized(BluetoothTetheringNetworkFactory.this) {
+ stopIpManagerLocked();
setScoreFilter(-1);
}
return;
}
synchronized(BluetoothTetheringNetworkFactory.this) {
- mLinkProperties = dhcpResults.toLinkProperties(
- linkProperties.getInterfaceName());
mNetworkInfo.setIsAvailable(true);
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
// Create our NetworkAgent.
mNetworkAgent = new NetworkAgent(getLooper(), mContext, NETWORK_TYPE,
- mNetworkInfo, mNetworkCapabilities, mLinkProperties, NETWORK_SCORE) {
+ mNetworkInfo, mNetworkCapabilities, linkProperties, NETWORK_SCORE) {
public void unwanted() {
BluetoothTetheringNetworkFactory.this.onCancelRequest();
};
@@ -121,7 +141,7 @@
}
}
});
- dhcpThread.start();
+ ipProvisioningThread.start();
}
// Called from NetworkFactory to indicate ConnectivityService no longer desires a Bluetooth
@@ -133,10 +153,9 @@
// Called by the NetworkFactory, NetworkAgent or PanService to tear down network.
private synchronized void onCancelRequest() {
- if (!TextUtils.isEmpty(mLinkProperties.getInterfaceName())) {
- NetworkUtils.stopDhcp(mLinkProperties.getInterfaceName());
- }
- mLinkProperties.clear();
+ stopIpManagerLocked();
+ mInterfaceName = "";
+
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -155,12 +174,11 @@
return;
}
synchronized(this) {
- if (mLinkProperties.getInterfaceName() != null) {
+ if (!TextUtils.isEmpty(mInterfaceName)) {
Slog.e(TAG, "attempted to reverse tether while already in process");
return;
}
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(iface);
+ mInterfaceName = iface;
// Advertise ourselves to ConnectivityService.
register();
setScoreFilter(NETWORK_SCORE);
@@ -170,7 +188,7 @@
// Called by PanService when a network interface for Bluetooth reverse-tethering
// goes away. We stop advertising ourselves to ConnectivityService at this point.
public synchronized void stopReverseTether() {
- if (TextUtils.isEmpty(mLinkProperties.getInterfaceName())) {
+ if (TextUtils.isEmpty(mInterfaceName)) {
Slog.e(TAG, "attempted to stop reverse tether with nothing tethered");
return;
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index caab6da..704ad1a 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -670,7 +670,7 @@
}
} else {
if (searchValue != null) {
- compareValue = searchValue.trim();
+ compareValue = searchValue.trim().toLowerCase();
}
for (int pos = listStartOffset; pos < listSize &&
itemsFound < requestSize; pos++) {
@@ -678,7 +678,7 @@
if (currentValue.contains(","))
currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
- if (searchValue.isEmpty() || ((currentValue.toLowerCase()).equals(compareValue.toLowerCase()))) {
+ if (searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith(compareValue))) {
itemsFound++;
writeVCardEntry(pos, currentValue,result);
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index 748e7a5..f4280ae 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -33,41 +33,38 @@
package com.android.bluetooth.pbap;
+import com.android.bluetooth.R;
+import com.android.bluetooth.util.DevicePolicyUtils;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardPhoneNumberTranslationCallback;
+
import android.content.ContentResolver;
import android.content.Context;
-import android.database.CursorWindowAllocationException;
import android.database.Cursor;
+import android.database.CursorWindowAllocationException;
import android.database.MatrixCursor;
import android.net.Uri;
import android.provider.CallLog;
-import android.provider.ContactsContract;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContactsEntity;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
-import com.android.bluetooth.R;
-import com.android.vcard.VCardComposer;
-import com.android.vcard.VCardConfig;
-import com.android.vcard.VCardPhoneNumberTranslationCallback;
-
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
-import javax.obex.ServerOperation;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
-
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.util.DevicePolicyUtils;
+import javax.obex.ServerOperation;
public class BluetoothPbapVcardManager {
private static final String TAG = "BluetoothPbapVcardManager";
@@ -261,23 +258,23 @@
final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
Cursor contactCursor = null;
+ // By default order is indexed
+ String orderBy = Phone.CONTACT_ID;
try {
- contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
- null, Phone.CONTACT_ID);
+ if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
+ orderBy = Phone.DISPLAY_NAME;
+ }
+ contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION,
+ CLAUSE_ONLY_VISIBLE, null, orderBy);
if (contactCursor != null) {
appendDistinctNameIdList(nameList,
mContext.getString(android.R.string.unknownName),
contactCursor);
- if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
- if (V) Log.v(TAG, "getPhonebookNameList, order by index");
- // Do not need to do anything, as we sort it by index already
- } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
- if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
- Collections.sort(nameList);
- }
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while getting phonebook name list");
} catch (Exception e) {
- Log.e(TAG, "Exception while getting Phonebook name list", e);
+ Log.e(TAG, "Exception while getting phonebook name list", e);
} finally {
if (contactCursor != null) {
contactCursor.close();
@@ -445,17 +442,22 @@
Cursor contactIdCursor = new MatrixCursor(new String[] {
Phone.CONTACT_ID
});
+ // By default order is indexed
+ String orderBy = Phone.CONTACT_ID;
try {
+ if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
+ orderBy = Phone.DISPLAY_NAME;
+ }
contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION,
- CLAUSE_ONLY_VISIBLE, null, Phone.CONTACT_ID);
- contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset);
-
+ CLAUSE_ONLY_VISIBLE, null, orderBy);
} catch (CursorWindowAllocationException e) {
Log.e(TAG,
- "CursorWindowAllocationException while composing phonebook one vcard");
+ "CursorWindowAllocationException while composing phonebook one vcard");
} finally {
if (contactCursor != null) {
+ contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset);
contactCursor.close();
+ contactCursor = null;
}
}
return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard,
@@ -743,18 +745,18 @@
public static class VCardFilter {
private static enum FilterBit {
- // bit property onlyCheckV21 excludeForV21
- FN ( 1, "FN", true, false),
- PHOTO( 3, "PHOTO", false, false),
- BDAY( 4, "BDAY", false, false),
- ADR( 5, "ADR", false, false),
- EMAIL( 8, "EMAIL", false, false),
- TITLE( 12, "TITLE", false, false),
- ORG( 16, "ORG", false, false),
- NOTE( 17, "NOTE", false, false),
- URL( 20, "URL", false, false),
- NICKNAME( 23, "NICKNAME", false, true),
- DATETIME( 28, "DATETIME", false, true);
+ // bit property onlyCheckV21 excludeForV21
+ FN ( 1, "FN", true, false),
+ PHOTO( 3, "PHOTO", false, false),
+ BDAY( 4, "BDAY", false, false),
+ ADR( 5, "ADR", false, false),
+ EMAIL( 8, "EMAIL", false, false),
+ TITLE( 12, "TITLE", false, false),
+ ORG( 16, "ORG", false, false),
+ NOTE( 17, "NOTE", false, false),
+ URL( 20, "URL", false, false),
+ NICKNAME( 23, "NICKNAME", false, true),
+ DATETIME( 28, "X-IRMC-CALL-DATETIME", false, false);
public final int pos;
public final String prop;
@@ -812,12 +814,19 @@
// Since PBAP does not have filter bits for IM and SIP,
// exclude them by default. Easiest way is to exclude all
- // X- fields....
- if (currentProp.startsWith("X-")) filteredIn = false;
+ // X- fields, except date time....
+ if (currentProp.startsWith("X-")) {
+ filteredIn = false;
+ if (currentProp.equals("X-IRMC-CALL-DATETIME")) {
+ filteredIn = true;
+ }
+ }
}
// Build filtered vCard
- if (filteredIn) filteredVCard.append(line + SEPARATOR);
+ if (filteredIn) {
+ filteredVCard.append(line + SEPARATOR);
+ }
}
return filteredVCard.toString();
@@ -830,7 +839,7 @@
/**
* Get size of the cursor without duplicated contact id. This assumes the
- * given cursor is sorted by CONATCT_ID.
+ * given cursor is sorted by CONTACT_ID.
*/
private static final int getDistinctContactIdSize(Cursor cursor) {
final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
@@ -853,14 +862,13 @@
/**
* Append "display_name,contact_id" string array from cursor to ArrayList.
- * This assumes the given cursor is sorted by CONATCT_ID.
+ * This assumes the given cursor is sorted by CONTACT_ID.
*/
private static void appendDistinctNameIdList(ArrayList<String> resultList,
String defaultName, Cursor cursor) {
final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
final int idColumn = cursor.getColumnIndex(Data._ID);
final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
- long previousContactId = -1;
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
final long contactId = cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
@@ -869,9 +877,9 @@
displayName = defaultName;
}
- if (previousContactId != contactId) {
- previousContactId = contactId;
- resultList.add(displayName + "," + contactId);
+ String newString = displayName + "," + contactId;
+ if (!resultList.contains(newString)) {
+ resultList.add(newString);
}
}
if (V) {
diff --git a/src/com/android/bluetooth/pbapclient/AuthenticationService.java b/src/com/android/bluetooth/pbapclient/AuthenticationService.java
new file mode 100644
index 0000000..cddfe15
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/AuthenticationService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.pbapsink;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class AuthenticationService extends Service {
+ private Authenticator mAuthenticator;
+
+ @Override
+ public void onCreate() {
+ mAuthenticator = new Authenticator(this);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mAuthenticator.getIBinder();
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/Authenticator.java b/src/com/android/bluetooth/pbapclient/Authenticator.java
new file mode 100644
index 0000000..4070ad2
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/Authenticator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.pbapsink;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+public class Authenticator extends AbstractAccountAuthenticator {
+ private static final String TAG = "PbapAuthenticator";
+
+ public Authenticator(Context context) {
+ super(context);
+ }
+
+ // Editing properties is not supported
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse r, String s) {
+ Log.d(TAG, "got call", new Exception());
+ throw new UnsupportedOperationException();
+ }
+
+ // Don't add additional accounts
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse r, String s, String s2, String[] strings,
+ Bundle bundle) throws NetworkErrorException {
+ Log.d(TAG, "got call", new Exception());
+ // Don't allow accounts to be added.
+ throw new UnsupportedOperationException();
+ }
+
+ // Ignore attempts to confirm credentials
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse r, Account account,
+ Bundle bundle) throws NetworkErrorException {
+ Log.d(TAG, "got call", new Exception());
+ return null;
+ }
+
+ // Getting an authentication token is not supported
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse r, Account account, String s,
+ Bundle bundle) throws NetworkErrorException {
+ Log.d(TAG, "got call", new Exception());
+ throw new UnsupportedOperationException();
+ }
+
+ // Getting a label for the auth token is not supported
+ @Override
+ public String getAuthTokenLabel(String s) {
+ Log.d(TAG, "got call", new Exception());
+ return null;
+ }
+
+ // Updating user credentials is not supported
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse r, Account account, String s,
+ Bundle bundle) throws NetworkErrorException {
+ Log.d(TAG, "got call", new Exception());
+ return null;
+ }
+
+ // Checking features for the account is not supported
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse r, Account account, String[] strings)
+ throws NetworkErrorException {
+ Log.d(TAG, "got call", new Exception());
+
+ final Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ return result;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapCard.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapCard.java
new file mode 100644
index 0000000..96422c5
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapCard.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.EmailData;
+import com.android.vcard.VCardEntry.NameData;
+import com.android.vcard.VCardEntry.PhoneData;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * Entry representation of folder listing
+ */
+public class BluetoothPbapCard {
+
+ public final String handle;
+
+ //Name to be parsed (N)
+ public final String N;
+ public final String lastName;
+ public final String firstName;
+ public final String middleName;
+ public final String prefix;
+ public final String suffix;
+
+ public BluetoothPbapCard(String handle, String name) {
+ this.handle = handle;
+
+ N = name;
+
+ /*
+ * format is as for vCard N field, so we have up to 5 tokens: LastName;
+ * FirstName; MiddleName; Prefix; Suffix
+ */
+ String[] parsedName = name.split(";", 5);
+
+ lastName = parsedName.length < 1 ? null : parsedName[0];
+ firstName = parsedName.length < 2 ? null : parsedName[1];
+ middleName = parsedName.length < 3 ? null : parsedName[2];
+ prefix = parsedName.length < 4 ? null : parsedName[3];
+ suffix = parsedName.length < 5 ? null : parsedName[4];
+ }
+
+ @Override
+ public String toString() {
+ JSONObject json = new JSONObject();
+
+ try {
+ json.put("handle", handle);
+ json.put("N", N);
+ json.put("lastName", lastName);
+ json.put("firstName", firstName);
+ json.put("middleName", middleName);
+ json.put("prefix", prefix);
+ json.put("suffix", suffix);
+ } catch (JSONException e) {
+ // do nothing
+ }
+
+ return json.toString();
+ }
+
+ static public String jsonifyVcardEntry(VCardEntry vcard) {
+ JSONObject json = new JSONObject();
+
+ try {
+ NameData name = vcard.getNameData();
+ json.put("formatted", name.getFormatted());
+ json.put("family", name.getFamily());
+ json.put("given", name.getGiven());
+ json.put("middle", name.getMiddle());
+ json.put("prefix", name.getPrefix());
+ json.put("suffix", name.getSuffix());
+ } catch (JSONException e) {
+ // do nothing
+ }
+
+ try {
+ JSONArray jsonPhones = new JSONArray();
+
+ List<PhoneData> phones = vcard.getPhoneList();
+
+ if (phones != null) {
+ for (PhoneData phone : phones) {
+ JSONObject jsonPhone = new JSONObject();
+ jsonPhone.put("type", phone.getType());
+ jsonPhone.put("number", phone.getNumber());
+ jsonPhone.put("label", phone.getLabel());
+ jsonPhone.put("is_primary", phone.isPrimary());
+
+ jsonPhones.put(jsonPhone);
+ }
+
+ json.put("phones", jsonPhones);
+ }
+ } catch (JSONException e) {
+ // do nothing
+ }
+
+ try {
+ JSONArray jsonEmails = new JSONArray();
+
+ List<EmailData> emails = vcard.getEmailList();
+
+ if (emails != null) {
+ for (EmailData email : emails) {
+ JSONObject jsonEmail = new JSONObject();
+ jsonEmail.put("type", email.getType());
+ jsonEmail.put("address", email.getAddress());
+ jsonEmail.put("label", email.getLabel());
+ jsonEmail.put("is_primary", email.isPrimary());
+
+ jsonEmails.put(jsonEmail);
+ }
+
+ json.put("emails", jsonEmails);
+ }
+ } catch (JSONException e) {
+ // do nothing
+ }
+
+ return json.toString();
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapClient.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapClient.java
new file mode 100644
index 0000000..6103928
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapClient.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+import android.bluetooth.BluetoothDevice;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Internal API to control Phone Book Profile (PCE role only).
+ * <p>
+ * This class defines methods that shall be used by application for the
+ * retrieval of phone book objects from remote device.
+ * <p>
+ * How to connect to remote device which is acting in PSE role:
+ * <ul>
+ * <li>Create a <code>BluetoothDevice</code> object which corresponds to remote
+ * device in PSE role;
+ * <li>Create an instance of <code>BluetoothPbapClient</code> class, passing
+ * <code>BluetothDevice</code> object along with a <code>Handler</code> to it;
+ * <li>Use {@link #setPhoneBookFolderRoot}, {@link #setPhoneBookFolderUp} and
+ * {@link #setPhoneBookFolderDown} to navigate in virtual phone book folder
+ * structure
+ * <li>Use {@link #pullPhoneBookSize} or {@link #pullVcardListingSize} to
+ * retrieve the size of selected phone book
+ * <li>Use {@link #pullPhoneBook} to retrieve phone book entries
+ * <li>Use {@link #pullVcardListing} to retrieve list of entries in the phone
+ * book
+ * <li>Use {@link #pullVcardEntry} to pull single entry from the phone book
+ * </ul>
+ * Upon completion of each call above PCE will notify application if operation
+ * completed successfully (along with results) or failed.
+ * <p>
+ * Therefore, application should handle following events in its message queue
+ * handler:
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_DONE</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_DONE</code>
+ * </ul>
+ * and
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_ERROR</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_ERROR</code>
+ * </ul>
+ * <code>connect</code> and <code>disconnect</code> methods are introduced for
+ * testing purposes. An application does not need to use them as the session
+ * connection and disconnection happens automatically internally.
+ */
+public class BluetoothPbapClient {
+ private static final boolean DBG = true;
+
+ private static final String TAG = "BluetoothPbapClient";
+
+ /**
+ * Path to local incoming calls history object
+ */
+ public static final String ICH_PATH = "telecom/ich.vcf";
+
+ /**
+ * Path to local outgoing calls history object
+ */
+ public static final String OCH_PATH = "telecom/och.vcf";
+
+ /**
+ * Path to local missed calls history object
+ */
+ public static final String MCH_PATH = "telecom/mch.vcf";
+
+ /**
+ * Path to local combined calls history object
+ */
+ public static final String CCH_PATH = "telecom/cch.vcf";
+
+ /**
+ * Path to local main phone book object
+ */
+ public static final String PB_PATH = "telecom/pb.vcf";
+
+ /**
+ * Path to incoming calls history object stored on the phone's SIM card
+ */
+ public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
+
+ /**
+ * Path to outgoing calls history object stored on the phone's SIM card
+ */
+ public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";
+
+ /**
+ * Path to missed calls history object stored on the phone's SIM card
+ */
+ public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
+
+ /**
+ * Path to combined calls history object stored on the phone's SIM card
+ */
+ public static final String SIM_CCH_PATH = "SIM1/telecom/cch.vcf";
+
+ /**
+ * Path to main phone book object stored on the phone's SIM card
+ */
+ public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
+
+ /**
+ * Indicates to server that default sorting order shall be used for vCard
+ * listing.
+ */
+ public static final byte ORDER_BY_DEFAULT = -1;
+
+ /**
+ * Indicates to server that indexed sorting order shall be used for vCard
+ * listing.
+ */
+ public static final byte ORDER_BY_INDEXED = 0;
+
+ /**
+ * Indicates to server that alphabetical sorting order shall be used for the
+ * vCard listing.
+ */
+ public static final byte ORDER_BY_ALPHABETICAL = 1;
+
+ /**
+ * Indicates to server that phonetical (based on sound attribute) sorting
+ * order shall be used for the vCard listing.
+ */
+ public static final byte ORDER_BY_PHONETIC = 2;
+
+ /**
+ * Indicates to server that Name attribute of vCard shall be used to carry
+ * out the search operation on
+ */
+ public static final byte SEARCH_ATTR_NAME = 0;
+
+ /**
+ * Indicates to server that Number attribute of vCard shall be used to carry
+ * out the search operation on
+ */
+ public static final byte SEARCH_ATTR_NUMBER = 1;
+
+ /**
+ * Indicates to server that Sound attribute of vCard shall be used to carry
+ * out the search operation
+ */
+ public static final byte SEARCH_ATTR_SOUND = 2;
+
+ /**
+ * VCard format version 2.1
+ */
+ public static final byte VCARD_TYPE_21 = 0;
+
+ /**
+ * VCard format version 3.0
+ */
+ public static final byte VCARD_TYPE_30 = 1;
+
+ /* 64-bit mask used to filter out VCard fields */
+ // TODO: Think of extracting to separate class
+ public static final long VCARD_ATTR_VERSION = 0x000000000000000001;
+ public static final long VCARD_ATTR_FN = 0x000000000000000002;
+ public static final long VCARD_ATTR_N = 0x000000000000000004;
+ public static final long VCARD_ATTR_PHOTO = 0x000000000000000008;
+ public static final long VCARD_ATTR_BDAY = 0x000000000000000010;
+ public static final long VCARD_ATTR_ADDR = 0x000000000000000020;
+ public static final long VCARD_ATTR_LABEL = 0x000000000000000040;
+ public static final long VCARD_ATTR_TEL = 0x000000000000000080;
+ public static final long VCARD_ATTR_EMAIL = 0x000000000000000100;
+ public static final long VCARD_ATTR_MAILER = 0x000000000000000200;
+ public static final long VCARD_ATTR_TZ = 0x000000000000000400;
+ public static final long VCARD_ATTR_GEO = 0x000000000000000800;
+ public static final long VCARD_ATTR_TITLE = 0x000000000000001000;
+ public static final long VCARD_ATTR_ROLE = 0x000000000000002000;
+ public static final long VCARD_ATTR_LOGO = 0x000000000000004000;
+ public static final long VCARD_ATTR_AGENT = 0x000000000000008000;
+ public static final long VCARD_ATTR_ORG = 0x000000000000010000;
+ public static final long VCARD_ATTR_NOTE = 0x000000000000020000;
+ public static final long VCARD_ATTR_REV = 0x000000000000040000;
+ public static final long VCARD_ATTR_SOUND = 0x000000000000080000;
+ public static final long VCARD_ATTR_URL = 0x000000000000100000;
+ public static final long VCARD_ATTR_UID = 0x000000000000200000;
+ public static final long VCARD_ATTR_KEY = 0x000000000000400000;
+ public static final long VCARD_ATTR_NICKNAME = 0x000000000000800000;
+ public static final long VCARD_ATTR_CATEGORIES = 0x000000000001000000;
+ public static final long VCARD_ATTR_PROID = 0x000000000002000000;
+ public static final long VCARD_ATTR_CLASS = 0x000000000004000000;
+ public static final long VCARD_ATTR_SORT_STRING = 0x000000000008000000;
+ public static final long VCARD_ATTR_X_IRMC_CALL_DATETIME =
+ 0x000000000010000000;
+
+ /**
+ * Maximal number of entries of the phone book that PCE can handle
+ */
+ public static final short MAX_LIST_COUNT = (short) 0xFFFF;
+
+ /**
+ * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+ * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+ * request.
+ * <p>
+ * This event indicates that request completed successfully.
+ * @see #setPhoneBookFolderRoot
+ * @see #setPhoneBookFolderUp
+ * @see #setPhoneBookFolderDown
+ */
+ public static final int EVENT_SET_PHONE_BOOK_DONE = 1;
+
+ /**
+ * Event propagated upon completion of <code>pullPhoneBook</code> request.
+ * <p>
+ * This event carry on results of the request.
+ * <p>
+ * The resulting message contains:
+ * <table>
+ * <tr>
+ * <td><code>msg.arg1</code></td>
+ * <td>newMissedCalls parameter (only in case of missed calls history object
+ * request)</td>
+ * </tr>
+ * <tr>
+ * <td><code>msg.obj</code></td>
+ * <td>which is a list of <code>VCardEntry</code> objects</td>
+ * </tr>
+ * </table>
+ * @see #pullPhoneBook
+ */
+ public static final int EVENT_PULL_PHONE_BOOK_DONE = 2;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardListing</code>
+ * request.
+ * <p>
+ * This event carry on results of the request.
+ * <p>
+ * The resulting message contains:
+ * <table>
+ * <tr>
+ * <td><code>msg.arg1</code></td>
+ * <td>newMissedCalls parameter (only in case of missed calls history object
+ * request)</td>
+ * </tr>
+ * <tr>
+ * <td><code>msg.obj</code></td>
+ * <td>which is a list of <code>BluetoothPbapCard</code> objects</td>
+ * </tr>
+ * </table>
+ * @see #pullVcardListing
+ */
+ public static final int EVENT_PULL_VCARD_LISTING_DONE = 3;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardEntry</code> request.
+ * <p>
+ * This event carry on results of the request.
+ * <p>
+ * The resulting message contains:
+ * <table>
+ * <tr>
+ * <td><code>msg.obj</code></td>
+ * <td>vCard as and object of type <code>VCardEntry</code></td>
+ * </tr>
+ * </table>
+ * @see #pullVcardEntry
+ */
+ public static final int EVENT_PULL_VCARD_ENTRY_DONE = 4;
+
+ /**
+ * Event propagated upon completion of <code>pullPhoneBookSize</code>
+ * request.
+ * <p>
+ * This event carry on results of the request.
+ * <p>
+ * The resulting message contains:
+ * <table>
+ * <tr>
+ * <td><code>msg.arg1</code></td>
+ * <td>size of the phone book</td>
+ * </tr>
+ * </table>
+ * @see #pullPhoneBookSize
+ */
+ public static final int EVENT_PULL_PHONE_BOOK_SIZE_DONE = 5;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardListingSize</code>
+ * request.
+ * <p>
+ * This event carry on results of the request.
+ * <p>
+ * The resulting message contains:
+ * <table>
+ * <tr>
+ * <td><code>msg.arg1</code></td>
+ * <td>size of the phone book listing</td>
+ * </tr>
+ * </table>
+ * @see #pullVcardListingSize
+ */
+ public static final int EVENT_PULL_VCARD_LISTING_SIZE_DONE = 6;
+
+ /**
+ * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+ * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+ * request. This event indicates an error during operation.
+ */
+ public static final int EVENT_SET_PHONE_BOOK_ERROR = 101;
+
+ /**
+ * Event propagated upon completion of <code>pullPhoneBook</code> request.
+ * This event indicates an error during operation.
+ */
+ public static final int EVENT_PULL_PHONE_BOOK_ERROR = 102;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardListing</code>
+ * request. This event indicates an error during operation.
+ */
+ public static final int EVENT_PULL_VCARD_LISTING_ERROR = 103;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardEntry</code> request.
+ * This event indicates an error during operation.
+ */
+ public static final int EVENT_PULL_VCARD_ENTRY_ERROR = 104;
+
+ /**
+ * Event propagated upon completion of <code>pullPhoneBookSize</code>
+ * request. This event indicates an error during operation.
+ */
+ public static final int EVENT_PULL_PHONE_BOOK_SIZE_ERROR = 105;
+
+ /**
+ * Event propagated upon completion of <code>pullVcardListingSize</code>
+ * request. This event indicates an error during operation.
+ */
+ public static final int EVENT_PULL_VCARD_LISTING_SIZE_ERROR = 106;
+
+ /**
+ * Event propagated when PCE has been connected to PSE
+ */
+ public static final int EVENT_SESSION_CONNECTED = 201;
+
+ /**
+ * Event propagated when PCE has been disconnected from PSE
+ */
+ public static final int EVENT_SESSION_DISCONNECTED = 202;
+ public static final int EVENT_SESSION_AUTH_REQUESTED = 203;
+ public static final int EVENT_SESSION_AUTH_TIMEOUT = 204;
+
+ public enum ConnectionState {
+ DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING;
+ }
+
+ private final Account mAccount;
+ private final Handler mClientHandler;
+ private final BluetoothPbapSession mSession;
+ private ConnectionState mConnectionState = ConnectionState.DISCONNECTED;
+
+ private SessionHandler mSessionHandler;
+
+ private static class SessionHandler extends Handler {
+
+ private final WeakReference<BluetoothPbapClient> mClient;
+
+ SessionHandler(BluetoothPbapClient client) {
+ mClient = new WeakReference<BluetoothPbapClient>(client);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ Log.d(TAG, "handleMessage: what=" + msg.what);
+
+ BluetoothPbapClient client = mClient.get();
+ if (client == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case BluetoothPbapSession.REQUEST_FAILED:
+ {
+ BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+ if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+ client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_ERROR);
+ } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+ client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_ERROR);
+ } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+ client.sendToClient(EVENT_PULL_PHONE_BOOK_ERROR);
+ } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+ client.sendToClient(EVENT_PULL_VCARD_LISTING_ERROR);
+ } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+ client.sendToClient(EVENT_PULL_VCARD_ENTRY_ERROR);
+ } else if (req instanceof BluetoothPbapRequestSetPath) {
+ client.sendToClient(EVENT_SET_PHONE_BOOK_ERROR);
+ }
+
+ break;
+ }
+
+ case BluetoothPbapSession.REQUEST_COMPLETED:
+ {
+ BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+ if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+ int size = ((BluetoothPbapRequestPullPhoneBookSize) req).getSize();
+ client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_DONE, size);
+
+ } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+ int size = ((BluetoothPbapRequestPullVcardListingSize) req).getSize();
+ client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_DONE, size);
+
+ } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+ BluetoothPbapRequestPullPhoneBook r = (BluetoothPbapRequestPullPhoneBook) req;
+ client.sendToClient(EVENT_PULL_PHONE_BOOK_DONE, r.getNewMissedCalls(),
+ r.getList());
+
+ } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+ BluetoothPbapRequestPullVcardListing r = (BluetoothPbapRequestPullVcardListing) req;
+ client.sendToClient(EVENT_PULL_VCARD_LISTING_DONE, r.getNewMissedCalls(),
+ r.getList());
+
+ } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+ BluetoothPbapRequestPullVcardEntry r = (BluetoothPbapRequestPullVcardEntry) req;
+ client.sendToClient(EVENT_PULL_VCARD_ENTRY_DONE, r.getVcard());
+
+ } else if (req instanceof BluetoothPbapRequestSetPath) {
+ client.sendToClient(EVENT_SET_PHONE_BOOK_DONE);
+ }
+
+ break;
+ }
+
+ case BluetoothPbapSession.AUTH_REQUESTED:
+ client.sendToClient(EVENT_SESSION_AUTH_REQUESTED);
+ break;
+
+ case BluetoothPbapSession.AUTH_TIMEOUT:
+ client.sendToClient(EVENT_SESSION_AUTH_TIMEOUT);
+ break;
+
+ /*
+ * app does not need to know when session is connected since
+ * OBEX session is managed inside BluetoothPbapSession
+ * automatically - we add this only so app can visualize PBAP
+ * connection status in case it wants to
+ */
+
+ case BluetoothPbapSession.SESSION_CONNECTING:
+ client.mConnectionState = ConnectionState.CONNECTING;
+ break;
+
+ case BluetoothPbapSession.SESSION_CONNECTED:
+ client.mConnectionState = ConnectionState.CONNECTED;
+ client.sendToClient(EVENT_SESSION_CONNECTED);
+ break;
+
+ case BluetoothPbapSession.SESSION_DISCONNECTED:
+ client.mConnectionState = ConnectionState.DISCONNECTED;
+ client.sendToClient(EVENT_SESSION_DISCONNECTED);
+ break;
+ }
+ }
+ };
+
+ private void sendToClient(int eventId) {
+ sendToClient(eventId, 0, null);
+ }
+
+ private void sendToClient(int eventId, int param) {
+ sendToClient(eventId, param, null);
+ }
+
+ private void sendToClient(int eventId, Object param) {
+ sendToClient(eventId, 0, param);
+ }
+
+ private void sendToClient(int eventId, int param1, Object param2) {
+ mClientHandler.obtainMessage(eventId, param1, 0, param2).sendToTarget();
+ }
+
+ /**
+ * Constructs PCE object
+ *
+ * @param device BluetoothDevice that corresponds to remote acting in PSE
+ * role
+ * @param account the account to which contacts will be added {@see #pullPhoneBook}.
+ * @param handler the handle that will be used by PCE to notify events and
+ * results to application
+ * @throws NullPointerException
+ */
+ public BluetoothPbapClient(BluetoothDevice device, Account account, Handler handler) {
+ if (DBG) {
+ Log.d(TAG, " device " + device + " account " + account);
+ }
+ if (device == null) {
+ throw new NullPointerException("BluetoothDevice is null");
+ }
+
+ mAccount = account;
+
+ mClientHandler = handler;
+
+ mSessionHandler = new SessionHandler(this);
+
+ mSession = new BluetoothPbapSession(device, mSessionHandler);
+ }
+
+ /**
+ * Starts a pbap session. <pb> This method set up rfcomm session, obex
+ * session and waits for requests to be transfered to PSE.
+ */
+ public void connect() {
+ mSession.start();
+ }
+
+ @Override
+ public void finalize() {
+ if (mSession != null) {
+ mSession.stop();
+ }
+ }
+
+ /**
+ * Stops all the active transactions and disconnects from the server.
+ */
+ public void disconnect() {
+ mSession.stop();
+ }
+
+ /**
+ * Aborts current request, if any
+ */
+ public void abort() {
+ mSession.abort();
+ }
+
+ public ConnectionState getState() {
+ return mConnectionState;
+ }
+
+ /**
+ * Sets current folder to root
+ *
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_SET_PHONE_BOOK_DONE} or
+ * {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean setPhoneBookFolderRoot() {
+ BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(false);
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Sets current folder to parent
+ *
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_SET_PHONE_BOOK_DONE} or
+ * {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean setPhoneBookFolderUp() {
+ BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(true);
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Sets current folder to selected sub-folder
+ *
+ * @param folder the name of the sub-folder
+ * @return @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_SET_PHONE_BOOK_DONE} or
+ * {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean setPhoneBookFolderDown(String folder) {
+ BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(folder);
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Requests for the number of entries in the phone book.
+ *
+ * @param pbName absolute path to the phone book
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_PHONE_BOOK_SIZE_DONE} or
+ * {@link #EVENT_PULL_PHONE_BOOK_SIZE_ERROR} in case of failure
+ */
+ public boolean pullPhoneBookSize(String pbName) {
+ BluetoothPbapRequestPullPhoneBookSize req = new BluetoothPbapRequestPullPhoneBookSize(
+ pbName);
+
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Requests for the number of entries in the phone book listing.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_SIZE_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_SIZE_ERROR} in case of failure
+ */
+ public boolean pullVcardListingSize(String folder) {
+ BluetoothPbapRequestPullVcardListingSize req = new BluetoothPbapRequestPullVcardListingSize(
+ folder);
+
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Pulls complete phone book. This method pulls phone book which entries are
+ * of <code>VCARD_TYPE_21</code> type and each single vCard contains minimal
+ * required set of fields and the number of entries in response is not
+ * limited.
+ *
+ * @param pbName absolute path to the phone book
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+ * {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean pullPhoneBook(String pbName) {
+ return pullPhoneBook(pbName, 0, VCARD_TYPE_21, 0, 0);
+ }
+
+ /**
+ * Pulls complete phone book. This method pulls all entries from the phone
+ * book.
+ *
+ * @param pbName absolute path to the phone book
+ * @param filter bit mask which indicates which fields of the vCard shall be
+ * included in each entry of the resulting list
+ * @param format vCard format of entries in the resulting list
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+ * {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean pullPhoneBook(String pbName, long filter, byte format) {
+ return pullPhoneBook(pbName, filter, format, 0, 0);
+ }
+
+ /**
+ * Pulls complete phone book. This method pulls entries from the phone book
+ * limited to the number of <code>maxListCount</code> starting from the
+ * position of <code>listStartOffset</code>.
+ * <p>
+ * The resulting list contains vCard objects in version
+ * <code>VCARD_TYPE_21</code> which in turns contain minimal required set of
+ * vCard fields.
+ *
+ * @param pbName absolute path to the phone book
+ * @param maxListCount limits number of entries in the response
+ * @param listStartOffset offset to the first entry of the list that would
+ * be returned
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+ * {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean pullPhoneBook(String pbName, int maxListCount, int listStartOffset) {
+ return pullPhoneBook(pbName, 0, VCARD_TYPE_21, maxListCount, listStartOffset);
+ }
+
+ /**
+ * Pulls complete phone book.
+ *
+ * @param pbName absolute path to the phone book
+ * @param filter bit mask which indicates which fields of the vCard hall be
+ * included in each entry of the resulting list
+ * @param format vCard format of entries in the resulting list
+ * @param maxListCount limits number of entries in the response
+ * @param listStartOffset offset to the first entry of the list that would
+ * be returned
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+ * {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+ */
+ public boolean pullPhoneBook(String pbName, long filter, byte format, int maxListCount,
+ int listStartOffset) {
+ BluetoothPbapRequest req = new BluetoothPbapRequestPullPhoneBook(
+ pbName, mAccount, filter, format, maxListCount, listStartOffset);
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Pulls list of entries in the phone book.
+ * <p>
+ * This method pulls the list of entries in the <code>folder</code>.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder) {
+ return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, 0, 0);
+ }
+
+ /**
+ * Pulls list of entries in the <code>folder</code>.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @param order the sorting order of the resulting list of entries
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder, byte order) {
+ return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, 0, 0);
+ }
+
+ /**
+ * Pulls list of entries in the <code>folder</code>. Only entries where
+ * <code>searchAttr</code> attribute of vCard matches <code>searchVal</code>
+ * will be listed.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @param searchAttr vCard attribute which shall be used to carry out search
+ * operation on
+ * @param searchVal text string used by matching routine to match the value
+ * of the attribute indicated by SearchAttr
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder, byte searchAttr, String searchVal) {
+ return pullVcardListing(folder, ORDER_BY_DEFAULT, searchAttr, searchVal, 0, 0);
+ }
+
+ /**
+ * Pulls list of entries in the <code>folder</code>.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @param order the sorting order of the resulting list of entries
+ * @param maxListCount limits number of entries in the response
+ * @param listStartOffset offset to the first entry of the list that would
+ * be returned
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder, byte order, int maxListCount,
+ int listStartOffset) {
+ return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, maxListCount,
+ listStartOffset);
+ }
+
+ /**
+ * Pulls list of entries in the <code>folder</code>.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @param maxListCount limits number of entries in the response
+ * @param listStartOffset offset to the first entry of the list that would
+ * be returned
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder, int maxListCount, int listStartOffset) {
+ return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, maxListCount,
+ listStartOffset);
+ }
+
+ /**
+ * Pulls list of entries in the <code>folder</code>.
+ *
+ * @param folder the name of the folder to be retrieved
+ * @param order the sorting order of the resulting list of entries
+ * @param searchAttr vCard attribute which shall be used to carry out search
+ * operation on
+ * @param searchVal text string used by matching routine to match the value
+ * of the attribute indicated by SearchAttr
+ * @param maxListCount limits number of entries in the response
+ * @param listStartOffset offset to the first entry of the list that would
+ * be returned
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+ * {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+ */
+ public boolean pullVcardListing(String folder, byte order, byte searchAttr,
+ String searchVal, int maxListCount, int listStartOffset) {
+ BluetoothPbapRequest req = new BluetoothPbapRequestPullVcardListing(folder, order,
+ searchAttr, searchVal, maxListCount, listStartOffset);
+ return mSession.makeRequest(req);
+ }
+
+ /**
+ * Pulls single vCard object
+ *
+ * @param handle handle to the vCard which shall be pulled
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_DONE} or
+ * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+ */
+ public boolean pullVcardEntry(String handle) {
+ return pullVcardEntry(handle, (byte) 0, VCARD_TYPE_21);
+ }
+
+ /**
+ * Pulls single vCard object
+ *
+ * @param handle handle to the vCard which shall be pulled
+ * @param filter bit mask of the vCard fields that shall be included in the
+ * resulting vCard
+ * @param format resulting vCard version
+ * @return <code>true</code> if request has been sent successfully;
+ * <code>false</code> otherwise; upon completion PCE sends
+ * {@link #EVENT_PULL_VCARD_DONE}
+ * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+ */
+ public boolean pullVcardEntry(String handle, long filter, byte format) {
+ BluetoothPbapRequest req =
+ new BluetoothPbapRequestPullVcardEntry(handle, mAccount, filter, format);
+ return mSession.makeRequest(req);
+ }
+
+ public boolean setAuthResponse(String key) {
+ Log.d(TAG, " setAuthResponse key=" + key);
+ return mSession.setAuthResponse(key);
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
new file mode 100644
index 0000000..e471d15
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import javax.obex.Authenticator;
+import javax.obex.PasswordAuthentication;
+
+class BluetoothPbapObexAuthenticator implements Authenticator {
+
+ private final static String TAG = "BluetoothPbapObexAuthenticator";
+
+ private String mSessionKey;
+
+ private boolean mReplied;
+
+ private final Handler mCallback;
+
+ public BluetoothPbapObexAuthenticator(Handler callback) {
+ mCallback = callback;
+ }
+
+ public synchronized void setReply(String key) {
+ Log.d(TAG, "setReply key=" + key);
+
+ mSessionKey = key;
+ mReplied = true;
+
+ notify();
+ }
+
+ @Override
+ public PasswordAuthentication onAuthenticationChallenge(String description,
+ boolean isUserIdRequired, boolean isFullAccess) {
+ PasswordAuthentication pa = null;
+
+ mReplied = false;
+
+ Log.d(TAG, "onAuthenticationChallenge: sending request");
+ mCallback.obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST)
+ .sendToTarget();
+
+ synchronized (this) {
+ while (!mReplied) {
+ try {
+ Log.v(TAG, "onAuthenticationChallenge: waiting for response");
+ this.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for challenge response");
+ }
+ }
+ }
+
+ if (mSessionKey != null && mSessionKey.length() != 0) {
+ Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
+ pa = new PasswordAuthentication(null, mSessionKey.getBytes());
+ } else {
+ Log.v(TAG, "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+ }
+
+ return pa;
+ }
+
+ @Override
+ public byte[] onAuthenticationResponse(byte[] userName) {
+ /* required only in case PCE challenges PSE which we don't do now */
+ return null;
+ }
+
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexSession.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexSession.java
new file mode 100644
index 0000000..55393d5
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexSession.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapObexSession {
+ private static final boolean DBG = true;
+ private static final String TAG = "BluetoothPbapObexSession";
+
+ private static final byte[] PBAP_TARGET = new byte[] {
+ 0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
+ 0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+ };
+
+ final static int OBEX_SESSION_CONNECTED = 100;
+ final static int OBEX_SESSION_FAILED = 101;
+ final static int OBEX_SESSION_DISCONNECTED = 102;
+ final static int OBEX_SESSION_REQUEST_COMPLETED = 103;
+ final static int OBEX_SESSION_REQUEST_FAILED = 104;
+ final static int OBEX_SESSION_AUTHENTICATION_REQUEST = 105;
+ final static int OBEX_SESSION_AUTHENTICATION_TIMEOUT = 106;
+
+ final static int MSG_CONNECT = 0;
+ final static int MSG_REQUEST = 1;
+
+ final static int CONNECTED = 0;
+ final static int CONNECTING = 1;
+ final static int DISCONNECTED = 2;
+
+ private Handler mSessionHandler;
+ private final ObexTransport mTransport;
+ // private ObexClientThread mObexClientThread;
+ private BluetoothPbapObexAuthenticator mAuth = null;
+ private HandlerThread mThread;
+ private Handler mHandler;
+ private ClientSession mClientSession;
+
+ private int mState = DISCONNECTED;
+
+ public BluetoothPbapObexSession(ObexTransport transport) {
+ mTransport = transport;
+ }
+
+ public synchronized boolean start(Handler handler) {
+ Log.d(TAG, "start");
+
+ if (mState == CONNECTED || mState == CONNECTING) {
+ return false;
+ }
+ mState = CONNECTING;
+ mSessionHandler = handler;
+
+ mAuth = new BluetoothPbapObexAuthenticator(mSessionHandler);
+
+ // Start the thread to process requests (see {@link schedule()}.
+ mThread = new HandlerThread("BluetoothPbapObexSessionThread");
+ mThread.start();
+ mHandler = new ObexClientHandler(mThread.getLooper(), this);
+
+ // Make connect call non blocking.
+ boolean status = mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+ if (!status) {
+ mState = DISCONNECTED;
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void stop() {
+ if (DBG) {
+ Log.d(TAG, "stop");
+ }
+
+ // This will essentially stop the handler and ignore any inflight requests.
+ mThread.quit();
+
+ // We clean up the state here.
+ disconnect(false /* no callback */);
+ }
+
+ public void abort() {
+ stop();
+ }
+
+ public boolean schedule(BluetoothPbapRequest request) {
+ if (DBG) {
+ Log.d(TAG, "schedule() called with: " + request);
+ }
+
+ boolean status = mHandler.sendMessage(mHandler.obtainMessage(MSG_REQUEST, request));
+ if (!status) {
+ Log.e(TAG, "Adding messages failed, obex must be disconnected.");
+ return false;
+ }
+ return true;
+ }
+
+ public int isConnected() {
+ return mState;
+ }
+
+ private void connect() {
+ if (DBG) {
+ Log.d(TAG, "connect()");
+ }
+
+ boolean success = true;
+ try {
+ mClientSession = new ClientSession(mTransport);
+ mClientSession.setAuthenticator(mAuth);
+ } catch (IOException e) {
+ Log.d(TAG, "connect() exception: " + e);
+ success = false;
+ }
+
+ HeaderSet hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TARGET, PBAP_TARGET);
+ try {
+ hs = mClientSession.connect(hs);
+
+ if (hs.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+ disconnect(true /* callback */);
+ success = false;
+ }
+ } catch (IOException e) {
+ success = false;
+ }
+
+ synchronized (this) {
+ if (success) {
+ mSessionHandler.obtainMessage(OBEX_SESSION_CONNECTED).sendToTarget();
+ mState = CONNECTED;
+ } else {
+ mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
+ mState = DISCONNECTED;
+ }
+ }
+ }
+
+ private synchronized void disconnect(boolean callback) {
+ if (DBG) {
+ Log.d(TAG, "disconnect()");
+ }
+
+ if (mState != DISCONNECTED) {
+ return;
+ }
+
+ if (mClientSession != null) {
+ try {
+ mClientSession.disconnect(null);
+ mClientSession.close();
+ } catch (IOException e) {
+ }
+ }
+
+ if (callback) {
+ mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
+ }
+
+ mState = DISCONNECTED;
+ }
+
+ private void executeRequest(BluetoothPbapRequest req) {
+ try {
+ req.execute(mClientSession);
+ } catch (IOException e) {
+ Log.e(TAG, "Error during executeRequest " + e);
+ disconnect(true);
+ }
+
+ if (req.isSuccess()) {
+ mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_COMPLETED, req).sendToTarget();
+ } else {
+ mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_FAILED, req).sendToTarget();
+ }
+ }
+
+ public boolean setAuthReply(String key) {
+ Log.d(TAG, "setAuthReply key=" + key);
+
+ if (mAuth == null) {
+ return false;
+ }
+
+ mAuth.setReply(key);
+
+ return true;
+ }
+
+ private static class ObexClientHandler extends Handler {
+ WeakReference<BluetoothPbapObexSession> mInst;
+
+ ObexClientHandler(Looper looper, BluetoothPbapObexSession inst) {
+ super(looper);
+ mInst = new WeakReference<BluetoothPbapObexSession>(inst);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ BluetoothPbapObexSession inst = mInst.get();
+ if (inst == null) {
+ Log.e(TAG, "The instance class is no longer around; terminating.");
+ return;
+ }
+
+ if (inst.isConnected() != CONNECTED && msg.what != MSG_CONNECT) {
+ Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_CONNECT:
+ inst.connect();
+ break;
+ case MSG_REQUEST:
+ inst.executeRequest((BluetoothPbapRequest) msg.obj);
+ break;
+ default:
+ Log.e(TAG, "Unknwown message type: " + msg.what);
+ }
+ }
+ }
+}
+
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java
new file mode 100644
index 0000000..8c2ff80
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+class BluetoothPbapObexTransport implements ObexTransport {
+
+ private BluetoothSocket mSocket = null;
+
+ public BluetoothPbapObexTransport(BluetoothSocket rfs) {
+ super();
+ mSocket = rfs;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mSocket.close();
+ }
+
+ @Override
+ public DataInputStream openDataInputStream() throws IOException {
+ return new DataInputStream(openInputStream());
+ }
+
+ @Override
+ public DataOutputStream openDataOutputStream() throws IOException {
+ return new DataOutputStream(openOutputStream());
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return mSocket.getInputStream();
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return mSocket.getOutputStream();
+ }
+
+ @Override
+ public void connect() throws IOException {
+ }
+
+ @Override
+ public void create() throws IOException {
+ }
+
+ @Override
+ public void disconnect() throws IOException {
+ }
+
+ @Override
+ public void listen() throws IOException {
+ }
+
+ public boolean isConnected() throws IOException {
+ // return true;
+ return mSocket.isConnected();
+ }
+
+ @Override
+ public int getMaxTransmitPacketSize() {
+ return -1;
+ }
+
+ @Override
+ public int getMaxReceivePacketSize() {
+ return -1;
+ }
+
+ @Override
+ public boolean isSrmSupported() {
+ return false;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
new file mode 100644
index 0000000..249cb8f
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+abstract class BluetoothPbapRequest {
+
+ private static final String TAG = "BluetoothPbapRequest";
+
+ protected static final byte OAP_TAGID_ORDER = 0x01;
+ protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
+ protected static final byte OAP_TAGID_SEARCH_ATTRIBUTE = 0x03;
+ protected static final byte OAP_TAGID_MAX_LIST_COUNT = 0x04;
+ protected static final byte OAP_TAGID_LIST_START_OFFSET = 0x05;
+ protected static final byte OAP_TAGID_FILTER = 0x06;
+ protected static final byte OAP_TAGID_FORMAT = 0x07;
+ protected static final byte OAP_TAGID_PHONEBOOK_SIZE = 0x08;
+ protected static final byte OAP_TAGID_NEW_MISSED_CALLS = 0x09;
+
+ protected HeaderSet mHeaderSet;
+
+ protected int mResponseCode;
+
+ private boolean mAborted = false;
+
+ private ClientOperation mOp = null;
+
+ public BluetoothPbapRequest() {
+ mHeaderSet = new HeaderSet();
+ }
+
+ final public boolean isSuccess() {
+ return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
+ }
+
+ public void execute(ClientSession session) throws IOException {
+ Log.v(TAG, "execute");
+
+ /* in case request is aborted before can be executed */
+ if (mAborted) {
+ mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ return;
+ }
+
+ try {
+ mOp = (ClientOperation) session.get(mHeaderSet);
+
+ /* make sure final flag for GET is used (PBAP spec 6.2.2) */
+ mOp.setGetFinalFlag(true);
+
+ /*
+ * this will trigger ClientOperation to use non-buffered stream so
+ * we can abort operation
+ */
+ mOp.continueOperation(true, false);
+
+ readResponseHeaders(mOp.getReceivedHeader());
+
+ InputStream is = mOp.openInputStream();
+ readResponse(is);
+ is.close();
+
+ mOp.close();
+
+ mResponseCode = mOp.getResponseCode();
+
+ Log.d(TAG, "mResponseCode=" + mResponseCode);
+
+ checkResponseCode(mResponseCode);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException occured when processing request", e);
+ mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+
+ throw e;
+ }
+ }
+
+ public void abort() {
+ mAborted = true;
+
+ if (mOp != null) {
+ try {
+ mOp.abort();
+ } catch (IOException e) {
+ Log.e(TAG, "Exception occured when trying to abort", e);
+ }
+ }
+ }
+
+ protected void readResponse(InputStream stream) throws IOException {
+ Log.v(TAG, "readResponse");
+
+ /* nothing here by default */
+ }
+
+ protected void readResponseHeaders(HeaderSet headerset) {
+ Log.v(TAG, "readResponseHeaders");
+
+ /* nothing here by dafault */
+ }
+
+ protected void checkResponseCode(int responseCode) throws IOException {
+ Log.v(TAG, "checkResponseCode");
+
+ /* nothing here by dafault */
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
new file mode 100644
index 0000000..6538bd5
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
+
+ private static final boolean DBG = true;
+
+ private static final String TAG = "BluetoothPbapRequestPullPhoneBook";
+
+ private static final String TYPE = "x-bt/phonebook";
+
+ private BluetoothPbapVcardList mResponse;
+
+ private Account mAccount;
+
+ private int mNewMissedCalls = -1;
+
+ private final byte mFormat;
+
+ public BluetoothPbapRequestPullPhoneBook(
+ String pbName, Account account, long filter, byte format,
+ int maxListCount, int listStartOffset) {
+ mAccount = account;
+ if (maxListCount < 0 || maxListCount > 65535) {
+ throw new IllegalArgumentException("maxListCount should be [0..65535]");
+ }
+
+ if (listStartOffset < 0 || listStartOffset > 65535) {
+ throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+ }
+
+ mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ ObexAppParameters oap = new ObexAppParameters();
+
+ /* make sure format is one of allowed values */
+ if (format != BluetoothPbapClient.VCARD_TYPE_21
+ && format != BluetoothPbapClient.VCARD_TYPE_30) {
+ format = BluetoothPbapClient.VCARD_TYPE_21;
+ }
+
+ if (filter != 0) {
+ oap.add(OAP_TAGID_FILTER, filter);
+ }
+
+ oap.add(OAP_TAGID_FORMAT, format);
+
+ /*
+ * maxListCount is a special case which is handled in
+ * BluetoothPbapRequestPullPhoneBookSize
+ */
+ if (maxListCount > 0) {
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+ } else {
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 65535);
+ }
+
+ if (listStartOffset > 0) {
+ oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+ }
+
+ oap.addToHeaderSet(mHeaderSet);
+
+ mFormat = format;
+ }
+
+ @Override
+ protected void readResponse(InputStream stream) throws IOException {
+ Log.v(TAG, "readResponse");
+
+ mResponse = new BluetoothPbapVcardList(mAccount, stream, mFormat);
+ if (DBG) {
+ Log.d(TAG, "Read " + mResponse.getCount() + " entries.");
+ }
+ }
+
+ @Override
+ protected void readResponseHeaders(HeaderSet headerset) {
+ Log.v(TAG, "readResponseHeaders");
+
+ ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+ if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+ mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+ }
+ }
+
+ public ArrayList<VCardEntry> getList() {
+ return mResponse.getList();
+ }
+
+ public int getNewMissedCalls() {
+ return mNewMissedCalls;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 0000000..29581d7
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+ private static final String TAG = "BluetoothPbapRequestPullPhoneBookSize";
+
+ private static final String TYPE = "x-bt/phonebook";
+
+ private int mSize;
+
+ public BluetoothPbapRequestPullPhoneBookSize(String pbName) {
+ mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ ObexAppParameters oap = new ObexAppParameters();
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+ oap.addToHeaderSet(mHeaderSet);
+ }
+
+ @Override
+ protected void readResponseHeaders(HeaderSet headerset) {
+ Log.v(TAG, "readResponseHeaders");
+
+ ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+ mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
new file mode 100644
index 0000000..128cdc2
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapRequestPullVcardEntry extends BluetoothPbapRequest {
+
+ private static final String TAG = "BluetoothPbapRequestPullVcardEntry";
+
+ private static final String TYPE = "x-bt/vcard";
+
+ private BluetoothPbapVcardList mResponse;
+
+ private final Account mAccount;
+
+ private final byte mFormat;
+
+ public BluetoothPbapRequestPullVcardEntry(
+ String handle, Account account, long filter, byte format) {
+ mAccount = account;
+
+ mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ /* make sure format is one of allowed values */
+ if (format != BluetoothPbapClient.VCARD_TYPE_21
+ && format != BluetoothPbapClient.VCARD_TYPE_30) {
+ format = BluetoothPbapClient.VCARD_TYPE_21;
+ }
+
+ ObexAppParameters oap = new ObexAppParameters();
+
+ if (filter != 0) {
+ oap.add(OAP_TAGID_FILTER, filter);
+ }
+
+ oap.add(OAP_TAGID_FORMAT, format);
+ oap.addToHeaderSet(mHeaderSet);
+
+ mFormat = format;
+ }
+
+ @Override
+ protected void readResponse(InputStream stream) throws IOException {
+ Log.v(TAG, "readResponse");
+
+ mResponse = new BluetoothPbapVcardList(mAccount, stream, mFormat);
+ }
+ @Override
+ protected void checkResponseCode(int responseCode) throws IOException {
+ Log.v(TAG, "checkResponseCode");
+
+ if (mResponse.getCount() == 0) {
+ if (responseCode != ResponseCodes.OBEX_HTTP_NOT_FOUND &&
+ responseCode != ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) {
+ throw new IOException("Invalid response received");
+ } else {
+ Log.v(TAG, "Vcard Entry not found");
+ }
+ }
+ }
+
+ public VCardEntry getVcard() {
+ return mResponse.getFirst();
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
new file mode 100644
index 0000000..ff4c617
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
+import com.android.bluetooth.pbapclient.BluetoothPbapVcardListing;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullVcardListing extends BluetoothPbapRequest {
+
+ private static final String TAG = "BluetoothPbapRequestPullVcardListing";
+
+ private static final String TYPE = "x-bt/vcard-listing";
+
+ private BluetoothPbapVcardListing mResponse = null;
+
+ private int mNewMissedCalls = -1;
+
+ public BluetoothPbapRequestPullVcardListing(String folder, byte order, byte searchAttr,
+ String searchVal, int maxListCount, int listStartOffset) {
+
+ if (maxListCount < 0 || maxListCount > 65535) {
+ throw new IllegalArgumentException("maxListCount should be [0..65535]");
+ }
+
+ if (listStartOffset < 0 || listStartOffset > 65535) {
+ throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+ }
+
+ if (folder == null) {
+ folder = "";
+ }
+
+ mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ ObexAppParameters oap = new ObexAppParameters();
+
+ if (order >= 0) {
+ oap.add(OAP_TAGID_ORDER, order);
+ }
+
+ if (searchVal != null) {
+ oap.add(OAP_TAGID_SEARCH_ATTRIBUTE, searchAttr);
+ oap.add(OAP_TAGID_SEARCH_VALUE, searchVal);
+ }
+
+ /*
+ * maxListCount is a special case which is handled in
+ * BluetoothPbapRequestPullVcardListingSize
+ */
+ if (maxListCount > 0) {
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+ }
+
+ if (listStartOffset > 0) {
+ oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+ }
+
+ oap.addToHeaderSet(mHeaderSet);
+ }
+
+ @Override
+ protected void readResponse(InputStream stream) throws IOException {
+ Log.v(TAG, "readResponse");
+
+ mResponse = new BluetoothPbapVcardListing(stream);
+ }
+
+ @Override
+ protected void readResponseHeaders(HeaderSet headerset) {
+ Log.v(TAG, "readResponseHeaders");
+
+ ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+ if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+ mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+ }
+ }
+
+ public ArrayList<BluetoothPbapCard> getList() {
+ return mResponse.getList();
+ }
+
+ public int getNewMissedCalls() {
+ return mNewMissedCalls;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
new file mode 100644
index 0000000..3ad289b
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullVcardListingSize extends BluetoothPbapRequest {
+
+ private static final String TAG = "BluetoothPbapRequestPullVcardListingSize";
+
+ private static final String TYPE = "x-bt/vcard-listing";
+
+ private int mSize;
+
+ public BluetoothPbapRequestPullVcardListingSize(String folder) {
+ mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+ mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+ ObexAppParameters oap = new ObexAppParameters();
+ oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+ oap.addToHeaderSet(mHeaderSet);
+ }
+
+ @Override
+ protected void readResponseHeaders(HeaderSet headerset) {
+ Log.v(TAG, "readResponseHeaders");
+
+ ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+ mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
new file mode 100644
index 0000000..55d64dd
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapRequestSetPath extends BluetoothPbapRequest {
+
+ private final static String TAG = "BluetoothPbapRequestSetPath";
+
+ private enum SetPathDir {
+ ROOT, UP, DOWN
+ };
+
+ private SetPathDir mDir;
+
+ public BluetoothPbapRequestSetPath(String name) {
+ mDir = SetPathDir.DOWN;
+ mHeaderSet.setHeader(HeaderSet.NAME, name);
+ }
+
+ public BluetoothPbapRequestSetPath(boolean goUp) {
+ mHeaderSet.setEmptyNameHeader();
+ if (goUp) {
+ mDir = SetPathDir.UP;
+ } else {
+ mDir = SetPathDir.ROOT;
+ }
+ }
+
+ @Override
+ public void execute(ClientSession session) {
+ Log.v(TAG, "execute");
+
+ HeaderSet hs = null;
+
+ try {
+ switch (mDir) {
+ case ROOT:
+ case DOWN:
+ hs = session.setPath(mHeaderSet, false, false);
+ break;
+ case UP:
+ hs = session.setPath(mHeaderSet, true, false);
+ break;
+ }
+
+ mResponseCode = hs.getResponseCode();
+ } catch (IOException e) {
+ mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapSession.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapSession.java
new file mode 100644
index 0000000..79aa95d
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapSession.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+class BluetoothPbapSession implements Callback {
+ //TODO consider cleaning file organization and naming.
+ private static final String TAG = "com.android.bluetooth.pbapclient.BluetoothPbapSession";
+
+ /* local use only */
+ private static final int RFCOMM_CONNECTED = 1;
+ private static final int RFCOMM_FAILED = 2;
+
+ /* to BluetoothPbapClient */
+ public static final int REQUEST_COMPLETED = 3;
+ public static final int REQUEST_FAILED = 4;
+ public static final int SESSION_CONNECTING = 5;
+ public static final int SESSION_CONNECTED = 6;
+ public static final int SESSION_DISCONNECTED = 7;
+ public static final int AUTH_REQUESTED = 8;
+ public static final int AUTH_TIMEOUT = 9;
+
+ public static final int ACTION_LISTING = 14;
+ public static final int ACTION_VCARD = 15;
+ public static final int ACTION_PHONEBOOK_SIZE = 16;
+
+ private static final String PBAP_UUID =
+ "0000112f-0000-1000-8000-00805f9b34fb";
+
+ private final BluetoothAdapter mAdapter;
+ private final BluetoothDevice mDevice;
+
+ private final Handler mParentHandler;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mSessionHandler;
+
+ private RfcommConnectThread mConnectThread;
+ private BluetoothPbapObexTransport mTransport;
+
+ private BluetoothPbapObexSession mObexSession;
+
+ private BluetoothPbapRequest mPendingRequest = null;
+
+ public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mAdapter == null) {
+ throw new NullPointerException("No Bluetooth adapter in the system");
+ }
+
+ mDevice = device;
+ mParentHandler = handler;
+ mConnectThread = null;
+ mTransport = null;
+ mObexSession = null;
+
+ mHandlerThread = new HandlerThread("PBAP session handler",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ Log.d(TAG, "Handler: msg: " + msg.what);
+
+ switch (msg.what) {
+ case RFCOMM_FAILED:
+ mConnectThread = null;
+
+ mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+ if (mPendingRequest != null) {
+ mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+ mPendingRequest = null;
+ }
+ break;
+
+ case RFCOMM_CONNECTED:
+ mConnectThread = null;
+ mTransport = (BluetoothPbapObexTransport) msg.obj;
+ startObexSession();
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
+ stopObexSession();
+
+ mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+ if (mPendingRequest != null) {
+ mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+ mPendingRequest = null;
+ }
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
+ mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
+
+ if (mPendingRequest != null) {
+ mObexSession.schedule(mPendingRequest);
+ mPendingRequest = null;
+ }
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
+ mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+ stopRfcomm();
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
+ /* send to parent, process there */
+ mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
+ /* send to parent, process there */
+ mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
+ /* send to parent, process there */
+ mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
+
+ mSessionHandler
+ .sendMessageDelayed(
+ mSessionHandler
+ .obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
+ 30000);
+ break;
+
+ case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
+ /* stop authentication */
+ setAuthResponse(null);
+
+ mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ public void start() {
+ Log.d(TAG, "start");
+
+ startRfcomm();
+ }
+
+ public void stop() {
+ Log.d(TAG, "Stop");
+
+ stopObexSession();
+ stopRfcomm();
+ }
+
+ public void abort() {
+ Log.d(TAG, "abort");
+
+ /* fail pending request immediately */
+ if (mPendingRequest != null) {
+ mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+ mPendingRequest = null;
+ }
+
+ if (mObexSession != null) {
+ mObexSession.abort();
+ }
+ }
+
+ public boolean makeRequest(BluetoothPbapRequest request) {
+ Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
+
+ if (mPendingRequest != null) {
+ Log.w(TAG, "makeRequest: request already queued, exiting");
+ return false;
+ }
+
+ if (mObexSession == null) {
+ mPendingRequest = request;
+
+ /*
+ * since there is no pending request and no session it's safe to
+ * assume that RFCOMM does not exist either and we should start
+ * connecting it
+ */
+ startRfcomm();
+
+ return true;
+ }
+
+ return mObexSession.schedule(request);
+ }
+
+ public boolean setAuthResponse(String key) {
+ Log.d(TAG, "setAuthResponse key=" + key);
+
+ mSessionHandler
+ .removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
+
+ /* does not make sense to set auth response when OBEX session is down */
+ if (mObexSession == null) {
+ return false;
+ }
+
+ return mObexSession.setAuthReply(key);
+ }
+
+ private void startRfcomm() {
+ Log.d(TAG, "startRfcomm");
+
+ if (mConnectThread == null && mObexSession == null) {
+ mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
+
+ mConnectThread = new RfcommConnectThread();
+ mConnectThread.start();
+ }
+
+ /*
+ * don't care if mConnectThread is not null - it means RFCOMM is being
+ * connected anyway
+ */
+ }
+
+ private void stopRfcomm() {
+ Log.d(TAG, "stopRfcomm");
+
+ if (mConnectThread != null) {
+ try {
+ // Force close the socket in case the thread is stuck doing the connect()
+ // call.
+ mConnectThread.closeSocket();
+ // TODO: Add timed join if closeSocket does not clean up the state.
+ mConnectThread.join();
+ } catch (InterruptedException e) {
+ }
+
+ mConnectThread = null;
+ }
+
+ if (mTransport != null) {
+ try {
+ mTransport.close();
+ } catch (IOException e) {
+ }
+
+ mTransport = null;
+ }
+ }
+
+ private void startObexSession() {
+ Log.d(TAG, "startObexSession");
+
+ mObexSession = new BluetoothPbapObexSession(mTransport);
+ mObexSession.start(mSessionHandler);
+ }
+
+ private void stopObexSession() {
+ Log.d(TAG, "stopObexSession");
+
+ if (mObexSession != null) {
+ mObexSession.stop();
+ mObexSession = null;
+ }
+ }
+
+ private class RfcommConnectThread extends Thread {
+ private static final String TAG = "RfcommConnectThread";
+
+ private BluetoothSocket mSocket;
+
+ public RfcommConnectThread() {
+ super("RfcommConnectThread");
+ }
+
+ @Override
+ public void run() {
+ if (mAdapter.isDiscovering()) {
+ mAdapter.cancelDiscovery();
+ }
+
+ try {
+ mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
+ mSocket.connect();
+
+ BluetoothPbapObexTransport transport;
+ transport = new BluetoothPbapObexTransport(mSocket);
+
+ mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
+ } catch (IOException e) {
+ closeSocket();
+ mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
+ }
+
+ }
+
+ // This method may be called from outside the thread if the connect() call above is stuck.
+ public void closeSocket() {
+ try {
+ if (mSocket != null) {
+ mSocket.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error when closing socket", e);
+ }
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java
new file mode 100644
index 0000000..1f22c14
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardList {
+
+ private final ArrayList<VCardEntry> mCards = new ArrayList<VCardEntry>();
+ private final Account mAccount;
+
+ class CardEntryHandler implements VCardEntryHandler {
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onEntryCreated(VCardEntry entry) {
+ mCards.add(entry);
+ }
+
+ @Override
+ public void onEnd() {
+ }
+ }
+
+ public BluetoothPbapVcardList(Account account, InputStream in, byte format) throws IOException {
+ mAccount = account;
+ parse(in, format);
+ }
+
+ private void parse(InputStream in, byte format) throws IOException {
+ VCardParser parser;
+
+ if (format == BluetoothPbapClient.VCARD_TYPE_30) {
+ parser = new VCardParser_V30();
+ } else {
+ parser = new VCardParser_V21();
+ }
+
+ VCardEntryConstructor constructor =
+ new VCardEntryConstructor(VCardConfig.VCARD_TYPE_V21_GENERIC, mAccount);
+ VCardEntryCounter counter = new VCardEntryCounter();
+ CardEntryHandler handler = new CardEntryHandler();
+
+ constructor.addEntryHandler(handler);
+
+ parser.addInterpreter(constructor);
+ parser.addInterpreter(counter);
+
+ try {
+ parser.parse(in);
+ } catch (VCardException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int getCount() {
+ return mCards.size();
+ }
+
+ public ArrayList<VCardEntry> getList() {
+ return mCards;
+ }
+
+ public VCardEntry getFirst() {
+ return mCards.get(0);
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardListing.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardListing.java
new file mode 100644
index 0000000..906cf65
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardListing.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardListing {
+
+ private static final String TAG = "BluetoothPbapVcardListing";
+
+ ArrayList<BluetoothPbapCard> mCards = new ArrayList<BluetoothPbapCard>();
+
+ public BluetoothPbapVcardListing(InputStream in) throws IOException {
+ parse(in);
+ }
+
+ private void parse(InputStream in) throws IOException {
+ XmlPullParser parser = Xml.newPullParser();
+
+ try {
+ parser.setInput(in, "UTF-8");
+
+ int eventType = parser.getEventType();
+
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("card")) {
+ BluetoothPbapCard card = new BluetoothPbapCard(
+ parser.getAttributeValue(null, "handle"),
+ parser.getAttributeValue(null, "name"));
+ mCards.add(card);
+ }
+
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "XML parser error when parsing XML", e);
+ }
+ }
+
+ public ArrayList<BluetoothPbapCard> getList() {
+ return mCards;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
new file mode 100644
index 0000000..6d1f3c0
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.pbapclient;
+
+import com.android.bluetooth.pbapclient.BluetoothPbapClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.os.RemoteException;
+import android.provider.CallLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.PhoneData;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class CallLogPullRequest extends PullRequest {
+ private static boolean DBG = true;
+ private static String TAG = "PbapCallLogPullRequest";
+ private static final String TIMESTAMP_PROPERTY = "X-IRMC-CALL-DATETIME";
+ private static final String TIMESTAMP_FORMAT = "yyyyMMdd'T'HHmmss";
+
+ private Context mContext;
+
+ public CallLogPullRequest(Context context, String path) {
+ mContext = context;
+ this.path = path;
+ }
+
+ @Override
+ public void onPullComplete() {
+ if (mEntries == null) {
+ Log.e(TAG, "onPullComplete entries is null.");
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "onPullComplete with " + mEntries.size() + " count.");
+ }
+ int type;
+ try {
+ if (path.equals(BluetoothPbapClient.ICH_PATH)) {
+ type = CallLog.Calls.INCOMING_TYPE;
+ } else if (path.equals(BluetoothPbapClient.OCH_PATH)) {
+ type = CallLog.Calls.OUTGOING_TYPE;
+ } else if (path.equals(BluetoothPbapClient.MCH_PATH)) {
+ type = CallLog.Calls.MISSED_TYPE;
+ } else {
+ Log.w(TAG, "Unknown path type:" + path);
+ return;
+ }
+
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (VCardEntry vcard : mEntries) {
+ List<PhoneData> phones = vcard.getPhoneList();
+ if (phones == null || phones.size() != 1) {
+ Log.d(TAG, "Incorrect number of phones: " + vcard);
+ continue;
+ }
+
+ List<Pair<String, String>> irmc = vcard.getUnknownXData();
+ Date date = null;
+ SimpleDateFormat parser = new SimpleDateFormat(TIMESTAMP_FORMAT);
+ for (Pair<String, String> pair : irmc) {
+ if (pair.first.startsWith(TIMESTAMP_PROPERTY)) {
+ try {
+ date = parser.parse(pair.second);
+ } catch (ParseException e) {
+ Log.d(TAG, "Failed to parse date " + pair.second);
+ }
+ }
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(CallLog.Calls.TYPE, type);
+ values.put(CallLog.Calls.NUMBER, phones.get(0).getNumber());
+ if (date != null) {
+ values.put(CallLog.Calls.DATE, date.getTime());
+ }
+ ops.add(ContentProviderOperation.newInsert(CallLog.Calls.CONTENT_URI)
+ .withValues(values).withYieldAllowed(true).build());
+ }
+ mContext.getContentResolver().applyBatch(CallLog.AUTHORITY, ops);
+ Log.d(TAG, "Updated call logs.");
+ } catch (RemoteException | OperationApplicationException e) {
+ Log.d(TAG, "Failed to update call log for path=" + path, e);
+ } finally {
+ synchronized (this) {
+ this.notify();
+ }
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
new file mode 100644
index 0000000..8af451c
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothPbapClient;
+import android.bluetooth.IBluetoothHeadsetClient;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderOperation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.OperationApplicationException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.provider.ContactsContract;
+
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
+import com.android.vcard.VCardEntry;
+
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+
+/**
+ * Provides Bluetooth Phone Book Access Profile Client profile.
+ *
+ * @hide
+ */
+public class PbapClientService extends ProfileService {
+ private static final boolean DBG = false;
+ private static final String TAG = "PbapClientService";
+ private PbapPCEClient mClient;
+ private HandlerThread mHandlerThread;
+ private AccountManager mAccountManager;
+ private static PbapClientService sPbapClientService;
+ private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
+
+ @Override
+ protected String getName() {
+ return TAG;
+ }
+
+ @Override
+ public IProfileServiceBinder initBinder() {
+ return new BluetoothPbapClientBinder(this);
+ }
+
+ @Override
+ protected boolean start() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ try {
+ registerReceiver(mPbapBroadcastReceiver, filter);
+ } catch (Exception e) {
+ Log.w(TAG,"Unable to register pbapclient receiver",e);
+ }
+ mClient = new PbapPCEClient(this);
+ mAccountManager = AccountManager.get(this);
+ setPbapClientService(this);
+ mClient.start();
+ return true;
+ }
+
+ @Override
+ protected boolean stop() {
+ try {
+ unregisterReceiver(mPbapBroadcastReceiver);
+ } catch (Exception e) {
+ Log.w(TAG,"Unable to unregister sap receiver",e);
+ }
+ mClient.disconnect(null);
+ return true;
+ }
+
+ @Override
+ protected boolean cleanup() {
+ clearPbapClientService();
+ return true;
+ }
+
+ private class PbapBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "onReceive");
+ String action = intent.getAction();
+ if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if(getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
+ connect(device);
+ }
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ disconnect(device);
+ }
+ }
+ }
+
+ /**
+ * Handler for incoming service calls
+ */
+ private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub
+ implements IProfileServiceBinder {
+ private PbapClientService mService;
+
+ public BluetoothPbapClientBinder(PbapClientService svc) {
+ mService = svc;
+ }
+
+ @Override
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ private PbapClientService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "PbapClient call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.disconnect(device);
+ }
+
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ PbapClientService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getConnectedDevices();
+ }
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return service.getConnectionState(device);
+ }
+
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setPriority(device, priority);
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ PbapClientService service = getService();
+ if (service == null) {
+ return BluetoothProfile.PRIORITY_UNDEFINED;
+ }
+ return service.getPriority(device);
+ }
+
+
+ }
+
+
+ // API methods
+ public static synchronized PbapClientService getPbapClientService() {
+ if (sPbapClientService != null && sPbapClientService.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "getPbapClientService(): returning " + sPbapClientService);
+ }
+ return sPbapClientService;
+ }
+ if (DBG) {
+ if (sPbapClientService == null) {
+ Log.d(TAG, "getPbapClientService(): service is NULL");
+ } else if (!(sPbapClientService.isAvailable())) {
+ Log.d(TAG, "getPbapClientService(): service is not available");
+ }
+ }
+ return null;
+ }
+
+ private static synchronized void setPbapClientService(PbapClientService instance) {
+ if (instance != null && instance.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "setPbapClientService(): set to: " + sPbapClientService);
+ }
+ sPbapClientService = instance;
+ } else {
+ if (DBG) {
+ if (sPbapClientService == null) {
+ Log.d(TAG, "setPbapClientService(): service not available");
+ } else if (!sPbapClientService.isAvailable()) {
+ Log.d(TAG, "setPbapClientService(): service is cleaning up");
+ }
+ }
+ }
+ }
+
+ private static synchronized void clearPbapClientService() {
+ sPbapClientService = null;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+ Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress());
+ int connectionState = mClient.getConnectionState();
+ if (connectionState == BluetoothProfile.STATE_CONNECTED ||
+ connectionState == BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ if (getPriority(device)>BluetoothProfile.PRIORITY_OFF) {
+ mClient.connect(device);
+ return true;
+ }
+ return false;
+ }
+
+ boolean disconnect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+ mClient.disconnect(device);
+ return true;
+ }
+ public List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
+ return getDevicesMatchingConnectionStates(desiredStates);
+ }
+
+ private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int clientState = mClient.getConnectionState();
+ Log.d(TAG,"getDevicesMatchingConnectionStates " + Arrays.toString(states) + " == " + clientState);
+ List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+ for (int state : states) {
+ if (clientState == state) {
+ BluetoothDevice currentDevice = mClient.getDevice();
+ if (currentDevice != null) {
+ deviceList.add(currentDevice);
+ }
+ }
+ }
+ return deviceList;
+ }
+
+ int getConnectionState(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (device == mClient.getDevice()) {
+ return mClient.getConnectionState();
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+ priority);
+ if (DBG) {
+ Log.d(TAG,"Saved priority " + device + " = " + priority);
+ }
+ return true;
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ int priority = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
+ BluetoothProfile.PRIORITY_UNDEFINED);
+ return priority;
+ }
+
+}
diff --git a/src/com/android/bluetooth/pbapclient/PbapHandler.java b/src/com/android/bluetooth/pbapclient/PbapHandler.java
new file mode 100644
index 0000000..36e7c0a
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PbapHandler.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.pbapclient;
+
+import com.android.bluetooth.pbapclient.BluetoothPbapCard;
+import com.android.bluetooth.pbapclient.BluetoothPbapClient;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+
+import java.util.List;
+
+public class PbapHandler extends Handler {
+ private static final String TAG = "PbapHandler";
+
+ private PbapListener mListener;
+
+ public PbapHandler(PbapListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ Log.d(TAG, "handleMessage " + msg.what);
+ switch (msg.what) {
+ case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_DONE:
+ Log.d(TAG, "EVENT_PULL_PHONE_BOOK_DONE with entries " +
+ ((List<VCardEntry>)(msg.obj)).size());
+ mListener.onPhoneBookPullDone((List<VCardEntry>) msg.obj);
+ break;
+ case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_ERROR:
+ Log.d(TAG, "EVENT_PULL_PHONE_BOOK_ERROR");
+ mListener.onPhoneBookError();
+ break;
+ case BluetoothPbapClient.EVENT_SESSION_CONNECTED:
+ Log.d(TAG, "EVENT_SESSION_CONNECTED");
+ mListener.onPbapClientConnected(true);
+ break;
+ case BluetoothPbapClient.EVENT_SESSION_DISCONNECTED:
+ Log.d(TAG, "EVENT_SESSION_DISCONNECTED");
+ mListener.onPbapClientConnected(false);
+ break;
+
+ // TODO(rni): Actually handle these cases.
+ case BluetoothPbapClient.EVENT_SET_PHONE_BOOK_DONE:
+ Log.d(TAG, "EVENT_SET_PHONE_BOOK_DONE");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_DONE:
+ Log.d(TAG, "EVENT_PULL_VCARD_LISTING_DONE");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_ENTRY_DONE:
+ Log.d(TAG, "EVENT_PULL_VCARD_ENTRY_DONE");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_SIZE_DONE:
+ Log.d(TAG, "EVENT_PULL_PHONE_BOOK_SIZE_DONE");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_SIZE_DONE:
+ Log.d(TAG, "EVENT_PULL_VCARD_LISTING_SIZE_DONE");
+ break;
+ case BluetoothPbapClient.EVENT_SET_PHONE_BOOK_ERROR:
+ Log.d(TAG, "EVENT_SET_PHONE_BOOK_ERROR");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_ERROR:
+ Log.d(TAG, "EVENT_PULL_VCARD_LISTING_ERROR");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_ENTRY_ERROR:
+ Log.d(TAG, "EVENT_PULL_VCARD_ENTRY_ERROR");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_SIZE_ERROR:
+ Log.d(TAG, "EVENT_PULL_PHONE_BOOK_SIZE_ERROR");
+ break;
+ case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_SIZE_ERROR:
+ Log.d(TAG, "EVENT_PULL_VCARD_LISTING_SIZE_ERROR");
+ break;
+ case BluetoothPbapClient.EVENT_SESSION_AUTH_REQUESTED:
+ Log.d(TAG, "EVENT_SESSION_AUTH_REQUESTED");
+ break;
+ case BluetoothPbapClient.EVENT_SESSION_AUTH_TIMEOUT:
+ Log.d(TAG, "EVENT_SESSION_AUTH_TIMEOUT");
+ break;
+ default:
+ Log.e(TAG, "unknown message " + msg);
+ break;
+ }
+ }
+
+ public interface PbapListener {
+ void onPhoneBookPullDone(List<VCardEntry> entries);
+ void onPhoneBookError();
+ void onPbapClientConnected(boolean status);
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PbapPCEClient.java b/src/com/android/bluetooth/pbapclient/PbapPCEClient.java
new file mode 100644
index 0000000..d04fabc
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PbapPCEClient.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.pbapclient;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.net.Uri;
+import android.provider.CallLog;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.vcard.VCardEntry;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.R;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.lang.InterruptedException;
+import java.lang.Thread;
+/**
+ * These are the possible paths that can be pulled:
+ * BluetoothPbapClient.PB_PATH;
+ * BluetoothPbapClient.SIM_PB_PATH;
+ * BluetoothPbapClient.ICH_PATH;
+ * BluetoothPbapClient.SIM_ICH_PATH;
+ * BluetoothPbapClient.OCH_PATH;
+ * BluetoothPbapClient.SIM_OCH_PATH;
+ * BluetoothPbapClient.MCH_PATH;
+ * BluetoothPbapClient.SIM_MCH_PATH;
+ */
+public class PbapPCEClient implements PbapHandler.PbapListener {
+ private static final String TAG = "PbapPCEClient";
+ private static final boolean DBG = false;
+ private final Queue<PullRequest> mPendingRequests = new ArrayDeque<PullRequest>();
+ private BluetoothDevice mDevice;
+ private BluetoothPbapClient mClient;
+ private boolean mClientConnected = false;
+ private PbapHandler mHandler;
+ private ConnectionHandler mConnectionHandler;
+ private PullRequest mLastPull;
+ private HandlerThread mContactHandlerThread;
+ private Handler mContactHandler;
+ private Account mAccount = null;
+ private Context mContext = null;
+ private AccountManager mAccountManager;
+
+ PbapPCEClient(Context context) {
+ mContext = context;
+ mConnectionHandler = new ConnectionHandler(mContext.getMainLooper());
+ mHandler = new PbapHandler(this);
+ mAccountManager = AccountManager.get(mContext);
+ mContactHandlerThread = new HandlerThread("PBAP contact handler",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mContactHandlerThread.start();
+ mContactHandler = new ContactHandler(mContactHandlerThread.getLooper());
+ }
+
+ public int getConnectionState() {
+ if (mDevice == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ BluetoothPbapClient.ConnectionState currentState = mClient.getState();
+ int bluetoothConnectionState;
+ switch(currentState) {
+ case DISCONNECTED:
+ bluetoothConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+ break;
+ case CONNECTING:
+ bluetoothConnectionState = BluetoothProfile.STATE_CONNECTING;
+ break;
+ case CONNECTED:
+ bluetoothConnectionState = BluetoothProfile.STATE_CONNECTED;
+ break;
+ case DISCONNECTING:
+ bluetoothConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+ break;
+ default:
+ bluetoothConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return bluetoothConnectionState;
+ }
+
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ private boolean processNextRequest() {
+ if (DBG) {
+ Log.d(TAG,"processNextRequest()");
+ }
+ if (mPendingRequests.isEmpty()) {
+ return false;
+ }
+ if (mClient != null && mClient.getState() ==
+ BluetoothPbapClient.ConnectionState.CONNECTED) {
+ mLastPull = mPendingRequests.remove();
+ if (DBG) {
+ Log.d(TAG, "Pulling phone book from: " + mLastPull.path);
+ }
+ return mClient.pullPhoneBook(mLastPull.path);
+ }
+ return false;
+ }
+
+
+ @Override
+ public void onPhoneBookPullDone(List<VCardEntry> entries) {
+ mLastPull.setResults(entries);
+ mContactHandler.obtainMessage(ContactHandler.EVENT_ADD_CONTACTS,mLastPull).sendToTarget();
+ processNextRequest();
+ }
+
+ @Override
+ public void onPhoneBookError() {
+ if (DBG) {
+ Log.d(TAG, "Error, mLastPull = " + mLastPull);
+ }
+ processNextRequest();
+ }
+
+ @Override
+ public synchronized void onPbapClientConnected(boolean status) {
+ mClientConnected = status;
+ if (mClientConnected == false) {
+ // If we are disconnected then whatever the current device is we should simply clean up.
+ onConnectionStateChanged(mDevice, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ disconnect(null);
+ }
+ if (mClientConnected == true) {
+ onConnectionStateChanged(mDevice, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ processNextRequest();
+ }
+ }
+
+ private class ConnectionHandler extends Handler {
+ public static final int EVENT_CONNECT = 1;
+ public static final int EVENT_DISCONNECT = 2;
+
+ public ConnectionHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) {
+ Log.d(TAG, "Connection Handler Message " + msg.what + " with " + msg.obj);
+ }
+ switch (msg.what) {
+ case EVENT_CONNECT:
+ if (msg.obj instanceof BluetoothDevice) {
+ BluetoothDevice device = (BluetoothDevice) msg.obj;
+ int oldState = getConnectionState();
+ if (oldState != BluetoothProfile.STATE_DISCONNECTED) {
+ return;
+ }
+ handleConnect(device);
+ } else {
+ Log.e(TAG, "Invalid instance in Connection Handler:Connect");
+ }
+ break;
+
+ case EVENT_DISCONNECT:
+ if (mDevice == null) {
+ return;
+ }
+ if (msg.obj == null || msg.obj instanceof BluetoothDevice) {
+ BluetoothDevice device = (BluetoothDevice) msg.obj;
+ if (!mDevice.equals(device)) {
+ return;
+ }
+ int oldState = getConnectionState();
+ handleDisconnect(device);
+ int newState = getConnectionState();
+ if (device != null) {
+ onConnectionStateChanged(device, oldState, newState);
+ }
+ } else {
+ Log.e(TAG, "Invalid instance in Connection Handler:Disconnect");
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown Request to Connection Handler");
+ break;
+ }
+ }
+
+ private void handleConnect(BluetoothDevice device) {
+ Log.d(TAG,"HANDLECONNECT" + device);
+ if (device == null) {
+ throw new IllegalStateException(TAG + ":Connect with null device!");
+ } else if (mDevice != null && !mDevice.equals(device)) {
+ // Check that we are not already connected to an existing different device.
+ // Since the device can be connected to multiple external devices -- we use the honor
+ // protocol and only accept the first connecting device.
+ Log.e(TAG, ":Got a connected event when connected to a different device. " +
+ "existing = " + mDevice + " new = " + device);
+ return;
+ } else if (device.equals(mDevice)) {
+ Log.w(TAG, "Got a connected event for the same device. Ignoring!");
+ return;
+ }
+ // Update the device.
+ mDevice = device;
+ onConnectionStateChanged(mDevice,BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ // Add the account. This should give us a place to stash the data.
+ mAccount = new Account(device.getAddress(),
+ mContext.getString(R.string.pbap_account_type));
+ mContactHandler.obtainMessage(ContactHandler.EVENT_ADD_ACCOUNT, mAccount)
+ .sendToTarget();
+ mClient = new BluetoothPbapClient(mDevice, mAccount, mHandler);
+ downloadPhoneBook();
+ downloadCallLogs();
+ mClient.connect();
+ }
+
+ private void handleDisconnect(BluetoothDevice device) {
+ Log.w(TAG, "pbap disconnecting from = " + device);
+
+ if (device == null) {
+ // If we have a null device then disconnect the current device.
+ device = mDevice;
+ } else if (mDevice == null) {
+ Log.w(TAG, "No existing device connected to service - ignoring device = " + device);
+ return;
+ } else if (!mDevice.equals(device)) {
+ Log.w(TAG, "Existing device different from disconnected device. existing = " + mDevice +
+ " disconnecting device = " + device);
+ return;
+ }
+ resetState();
+ }
+ }
+
+ private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
+ Intent intent = new Intent(android.bluetooth.BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ Log.d(TAG,"Connection state " + device + ": " + prevState + "->" + state);
+ }
+
+ public void connect(BluetoothDevice device) {
+ mConnectionHandler.obtainMessage(ConnectionHandler.EVENT_CONNECT,device).sendToTarget();
+ }
+
+ public void disconnect(BluetoothDevice device) {
+ mConnectionHandler.obtainMessage(ConnectionHandler.EVENT_DISCONNECT,device).sendToTarget();
+ }
+
+ public void start() {
+ if (mDevice != null) {
+ // We are already connected -Ignore.
+ Log.w(TAG, "Already started, ignoring request to start again.");
+ return;
+ }
+ // Device is NULL, we go on remove any unclean shutdown accounts.
+ mContactHandler.obtainMessage(ContactHandler.EVENT_CLEANUP).sendToTarget();
+ }
+
+ private void resetState() {
+ if (DBG) {
+ Log.d(TAG,"resetState()");
+ }
+ if (mClient != null) {
+ // This should abort any inflight messages.
+ mClient.disconnect();
+ }
+ mClient = null;
+ mClientConnected = false;
+
+ mContactHandler.removeCallbacksAndMessages(null);
+ mContactHandlerThread.interrupt();
+ mContactHandler.obtainMessage(ContactHandler.EVENT_CLEANUP).sendToTarget();
+
+ mDevice = null;
+ mAccount = null;
+ mPendingRequests.clear();
+ if (DBG) {
+ Log.d(TAG,"resetState Complete");
+ }
+
+ }
+
+ private void downloadCallLogs() {
+ // Download Incoming Call Logs.
+ CallLogPullRequest ichCallLog =
+ new CallLogPullRequest(mContext, BluetoothPbapClient.ICH_PATH);
+ addPullRequest(ichCallLog);
+
+ // Downoad Outgoing Call Logs.
+ CallLogPullRequest ochCallLog =
+ new CallLogPullRequest(mContext, BluetoothPbapClient.OCH_PATH);
+ addPullRequest(ochCallLog);
+
+ // Downoad Missed Call Logs.
+ CallLogPullRequest mchCallLog =
+ new CallLogPullRequest(mContext, BluetoothPbapClient.MCH_PATH);
+ addPullRequest(mchCallLog);
+ }
+
+ private void downloadPhoneBook() {
+ // Download the phone book.
+ PhonebookPullRequest pb = new PhonebookPullRequest(mContext, mAccount);
+ addPullRequest(pb);
+ }
+
+ private void addPullRequest(PullRequest r) {
+ if (DBG) {
+ Log.d(TAG, "pull request mClient=" + mClient + " connected= " +
+ mClientConnected + " mDevice=" + mDevice + " path= " + r.path);
+ }
+ if (mClient == null || mDevice == null) {
+ // It seems we want to pull but the bt connection isn't up, fail it
+ // immediately.
+ Log.w(TAG, "aborting pull request.");
+ return;
+ }
+ mPendingRequests.add(r);
+ }
+
+ private class ContactHandler extends Handler {
+ public static final int EVENT_ADD_ACCOUNT = 1;
+ public static final int EVENT_ADD_CONTACTS = 2;
+ public static final int EVENT_CLEANUP = 3;
+
+ public ContactHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) {
+ Log.d(TAG, "Contact Handler Message " + msg.what + " with " + msg.obj);
+ }
+ switch (msg.what) {
+ case EVENT_ADD_ACCOUNT:
+ if (msg.obj instanceof Account) {
+ Account account = (Account) msg.obj;
+ addAccount(account);
+ } else {
+ Log.e(TAG, "invalid Instance in Contact Handler: Add Account");
+ }
+ break;
+
+ case EVENT_ADD_CONTACTS:
+ if (msg.obj instanceof PullRequest) {
+ PullRequest req = (PullRequest) msg.obj;
+ req.onPullComplete();
+ } else {
+ Log.e(TAG, "invalid Instance in Contact Handler: Add Contacts");
+ }
+ break;
+
+ case EVENT_CLEANUP:
+ Thread.currentThread().interrupted(); //clear state of interrupt.
+ removeUncleanAccounts();
+ mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null);
+ if (DBG) {
+ Log.d(TAG, "Call logs deleted.");
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown Request to Contact Handler");
+ break;
+ }
+ }
+
+ private void removeUncleanAccounts() {
+ // Find all accounts that match the type "pbap" and delete them. This section is
+ // executed only if the device was shut down in an unclean state and contacts persisted.
+ Account[] accounts =
+ mAccountManager.getAccountsByType(mContext.getString(R.string.pbap_account_type));
+ Log.w(TAG, "Found " + accounts.length + " unclean accounts");
+ for (Account acc : accounts) {
+ Log.w(TAG, "Deleting " + acc);
+ // The device ID is the name of the account.
+ removeAccount(acc);
+ }
+ }
+
+ private boolean addAccount(Account account) {
+ if (mAccountManager.addAccountExplicitly(account, null, null)) {
+ if (DBG) {
+ Log.d(TAG, "Added account " + mAccount);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean removeAccount(Account acc) {
+ if (mAccountManager.removeAccountExplicitly(acc)) {
+ if (DBG) {
+ Log.d(TAG, "Removed account " + acc);
+ }
+ return true;
+ }
+ Log.e(TAG, "Failed to remove account " + mAccount);
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookEntry.java b/src/com/android/bluetooth/pbapclient/PhonebookEntry.java
new file mode 100644
index 0000000..4d38f35
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PhonebookEntry.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * A simpler more public version of VCardEntry.
+ */
+public class PhonebookEntry {
+ public static class Name {
+ public String family;
+ public String given;
+ public String middle;
+ public String prefix;
+ public String suffix;
+
+ public Name() { }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Name)) {
+ return false;
+ }
+
+ Name n = ((Name) o);
+ return (family == n.family || family != null && family.equals(n.family)) &&
+ (given == n.given || given != null && given.equals(n.given)) &&
+ (middle == n.middle || middle != null && middle.equals(n.middle)) &&
+ (prefix == n.prefix || prefix != null && prefix.equals(n.prefix)) &&
+ (suffix == n.suffix || suffix != null && suffix.equals(n.suffix));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 23 * (family == null ? 0 : family.hashCode());
+ result = 23 * result + (given == null ? 0 : given.hashCode());
+ result = 23 * result + (middle == null ? 0 : middle.hashCode());
+ result = 23 * result + (prefix == null ? 0 : prefix.hashCode());
+ result = 23 * result + (suffix == null ? 0 : suffix.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Name: { family: ");
+ sb.append(family);
+ sb.append(" given: ");
+ sb.append(given);
+ sb.append(" middle: ");
+ sb.append(middle);
+ sb.append(" prefix: ");
+ sb.append(prefix);
+ sb.append(" suffix: ");
+ sb.append(suffix);
+ sb.append(" }");
+ return sb.toString();
+ }
+ }
+
+ public static class Phone {
+ public int type;
+ public String number;
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Phone)) {
+ return false;
+ }
+
+ Phone p = (Phone) o;
+ return (number == p.number || number != null && number.equals(p.number))
+ && type == p.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return 23 * type + number.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" Phone: { number: ");
+ sb.append(number);
+ sb.append(" type: " + type);
+ sb.append(" }");
+ return sb.toString();
+ }
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof PhonebookEntry) {
+ return equals((PhonebookEntry) object);
+ }
+ return false;
+ }
+
+ public PhonebookEntry() {
+ name = new Name();
+ phones = new ArrayList<Phone>();
+ }
+
+ public PhonebookEntry(VCardEntry v) {
+ name = new Name();
+ phones = new ArrayList<Phone>();
+
+ VCardEntry.NameData n = v.getNameData();
+ name.family = n.getFamily();
+ name.given = n.getGiven();
+ name.middle = n.getMiddle();
+ name.prefix = n.getPrefix();
+ name.suffix = n.getSuffix();
+
+ List<VCardEntry.PhoneData> vp = v.getPhoneList();
+ if (vp == null || vp.isEmpty()) {
+ return;
+ }
+
+ for (VCardEntry.PhoneData p : vp) {
+ Phone phone = new Phone();
+ phone.type = p.getType();
+ phone.number = p.getNumber();
+ phones.add(phone);
+ }
+ }
+
+ private boolean equals(PhonebookEntry p) {
+ return name.equals(p.name) && phones.equals(p.phones);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode() + 23 * phones.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("PhonebookEntry { id: ");
+ sb.append(id);
+ sb.append(" ");
+ sb.append(name.toString());
+ sb.append(phones.toString());
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ public Name name;
+ public List<Phone> phones;
+ public String id;
+}
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
new file mode 100644
index 0000000..5e95aef
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
@@ -0,0 +1,238 @@
+package com.android.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+
+import android.accounts.Account;
+import com.android.bluetooth.pbapclient.BluetoothPbapClient;
+import android.content.ContentProviderOperation;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.provider.ContactsContract;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.provider.ContactsContract.Contacts.Entity;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+
+import java.lang.InterruptedException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class PhonebookPullRequest extends PullRequest {
+ private static final int MAX_OPS = 200;
+ private static final boolean DBG = true;
+ private static final String TAG = "PbapPhonebookPullRequest";
+
+ private final Account mAccount;
+ private final Context mContext;
+ public boolean complete = false;
+
+ public PhonebookPullRequest(Context context, Account account) {
+ mContext = context;
+ mAccount = account;
+ path = BluetoothPbapClient.PB_PATH;
+ }
+
+ private PhonebookEntry fetchContact(String id) {
+ PhonebookEntry entry = new PhonebookEntry();
+ entry.id = id;
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(
+ Data.CONTENT_URI,
+ null,
+ Data.RAW_CONTACT_ID + " = ?",
+ new String[] { id },
+ null);
+ if (c != null) {
+ int mimeTypeIndex = c.getColumnIndex(Data.MIMETYPE);
+ int familyNameIndex = c.getColumnIndex(StructuredName.FAMILY_NAME);
+ int givenNameIndex = c.getColumnIndex(StructuredName.GIVEN_NAME);
+ int middleNameIndex = c.getColumnIndex(StructuredName.MIDDLE_NAME);
+ int prefixIndex = c.getColumnIndex(StructuredName.PREFIX);
+ int suffixIndex = c.getColumnIndex(StructuredName.SUFFIX);
+
+ int phoneTypeIndex = c.getColumnIndex(Phone.TYPE);
+ int phoneNumberIndex = c.getColumnIndex(Phone.NUMBER);
+
+ while (c.moveToNext()) {
+ String mimeType = c.getString(mimeTypeIndex);
+ if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
+ entry.name.family = c.getString(familyNameIndex);
+ entry.name.given = c.getString(givenNameIndex);
+ entry.name.middle = c.getString(middleNameIndex);
+ entry.name.prefix = c.getString(prefixIndex);
+ entry.name.suffix = c.getString(suffixIndex);
+ } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
+ PhonebookEntry.Phone p = new PhonebookEntry.Phone();
+ p.type = c.getInt(phoneTypeIndex);
+ p.number = c.getString(phoneNumberIndex);
+ entry.phones.add(p);
+ }
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return entry;
+ }
+
+ private HashMap<PhonebookEntry.Name, PhonebookEntry> fetchExistingContacts() {
+ HashMap<PhonebookEntry.Name, PhonebookEntry> entries = new HashMap<>();
+
+ Cursor c = null;
+ try {
+ // First find all the contacts present. Fetch all rows.
+ Uri uri = RawContacts.CONTENT_URI.buildUpon()
+ .appendQueryParameter(RawContacts.ACCOUNT_NAME, mAccount.name)
+ .appendQueryParameter(RawContacts.ACCOUNT_TYPE, mAccount.type)
+ .build();
+ // First get all the raw contact ids.
+ c = mContext.getContentResolver().query(uri,
+ new String[] { RawContacts._ID },
+ null, null, null);
+
+ if (c != null) {
+ while (c.moveToNext()) {
+ // For each raw contact id, fetch all the data.
+ PhonebookEntry e = fetchContact(c.getString(0));
+ entries.put(e.name, e);
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ return entries;
+ }
+
+ private void addContacts(List<PhonebookEntry> entries)
+ throws RemoteException, OperationApplicationException, InterruptedException {
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ for (PhonebookEntry e : entries) {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ int index = ops.size();
+ // Add an entry.
+ ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
+ .withValue(RawContacts.ACCOUNT_TYPE, mAccount.type)
+ .withValue(RawContacts.ACCOUNT_NAME, mAccount.name)
+ .build());
+
+ // Populate the name.
+ ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, index)
+ .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(StructuredName.FAMILY_NAME , e.name.family)
+ .withValue(StructuredName.GIVEN_NAME , e.name.given)
+ .withValue(StructuredName.MIDDLE_NAME , e.name.middle)
+ .withValue(StructuredName.PREFIX , e.name.prefix)
+ .withValue(StructuredName.SUFFIX , e.name.suffix)
+ .build());
+
+ // Populate the phone number(s) if any.
+ for (PhonebookEntry.Phone p : e.phones) {
+ ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, index)
+ .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
+ .withValue(Phone.NUMBER, p.number)
+ .withValue(Phone.TYPE, p.type)
+ .build());
+ }
+
+ // Commit MAX_OPS at a time so that the binder transaction doesn't get too large.
+ if (ops.size() > MAX_OPS) {
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+ ops.clear();
+ }
+ }
+
+ if (ops.size() > 0) {
+ // Commit remaining entries.
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+ }
+ }
+
+ private void deleteContacts(List<PhonebookEntry> entries)
+ throws RemoteException, OperationApplicationException {
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ for (PhonebookEntry e : entries) {
+ ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI.buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+ .build())
+ .withSelection(RawContacts._ID + "=?", new String[] { e.id })
+ .build());
+ }
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+ }
+
+ @Override
+ public void onPullComplete() {
+ if (mEntries == null) {
+ Log.e(TAG, "onPullComplete entries is null.");
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "onPullComplete with " + mEntries.size() + " count.");
+ }
+ try {
+
+ HashMap<PhonebookEntry.Name, PhonebookEntry> contacts = fetchExistingContacts();
+
+ List<PhonebookEntry> contactsToAdd = new ArrayList<PhonebookEntry>();
+ List<PhonebookEntry> contactsToDelete = new ArrayList<PhonebookEntry>();
+
+ for (VCardEntry e : mEntries) {
+ PhonebookEntry current = new PhonebookEntry(e);
+ PhonebookEntry.Name key = current.name;
+
+ PhonebookEntry contact = contacts.get(key);
+ if (contact == null) {
+ contactsToAdd.add(current);
+ } else if (!contact.equals(current)) {
+ // Instead of trying to figure out what changed on an update, do a delete
+ // and an add. Sure, it churns contact ids but a contact being updated
+ // while someone is connected is a low enough frequency event that the
+ // complexity of doing an update is just not worth it.
+ contactsToAdd.add(current);
+ // Don't remove it from the hashmap so it will get deleted.
+ } else {
+ contacts.remove(key);
+ }
+ }
+ contactsToDelete.addAll(contacts.values());
+
+ if (!contactsToDelete.isEmpty()) {
+ deleteContacts(contactsToDelete);
+ }
+
+ if (!contactsToAdd.isEmpty()) {
+ addContacts(contactsToAdd);
+ }
+
+ Log.d(TAG, "Sync complete: add=" + contactsToAdd.size()
+ + " delete=" + contactsToDelete.size());
+ } catch (OperationApplicationException | RemoteException | NumberFormatException e) {
+ Log.d(TAG, "Got exception: ", e);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "Interrupted durring insert.");
+ } finally {
+ complete = true;
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/PullRequest.java b/src/com/android/bluetooth/pbapclient/PullRequest.java
new file mode 100644
index 0000000..4944f85
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/PullRequest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+
+import java.util.List;
+
+public abstract class PullRequest {
+ public String path;
+ protected List<VCardEntry> mEntries;
+ public abstract void onPullComplete();
+
+ @Override
+ public String toString() {
+ return "PullRequest: { path=" + path + " }";
+ }
+
+ public void setResults(List<VCardEntry> results) {
+ mEntries = results;
+ }
+}
+
diff --git a/src/com/android/bluetooth/pbapclient/utils/BmsgTokenizer.java b/src/com/android/bluetooth/pbapclient/utils/BmsgTokenizer.java
new file mode 100644
index 0000000..3888f3f
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/utils/BmsgTokenizer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient.utils;
+
+import android.util.Log;
+
+import java.text.ParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class BmsgTokenizer {
+
+ private final String mStr;
+
+ private final Matcher mMatcher;
+
+ private int mPos = 0;
+
+ private final int mOffset;
+
+ static public class Property {
+ public final String name;
+ public final String value;
+
+ public Property(String name, String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.name = name;
+ this.value = value;
+
+ Log.v("BMSG >> ", toString());
+ }
+
+ @Override
+ public String toString() {
+ return name + ":" + value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return ((o instanceof Property) && ((Property) o).name.equals(name) && ((Property) o).value
+ .equals(value));
+ }
+ };
+
+ public BmsgTokenizer(String str) {
+ this(str, 0);
+ }
+
+ public BmsgTokenizer(String str, int offset) {
+ mStr = str;
+ mOffset = offset;
+ mMatcher = Pattern.compile("(([^:]*):(.*))?\r\n").matcher(str);
+ mPos = mMatcher.regionStart();
+ }
+
+ public Property next(boolean alwaysReturn) throws ParseException {
+ boolean found = false;
+
+ do {
+ mMatcher.region(mPos, mMatcher.regionEnd());
+
+ if (!mMatcher.lookingAt()) {
+ if (alwaysReturn) {
+ return null;
+ }
+
+ throw new ParseException("Property or empty line expected", pos());
+ }
+
+ mPos = mMatcher.end();
+
+ if (mMatcher.group(1) != null) {
+ found = true;
+ }
+ } while (!found);
+
+ return new Property(mMatcher.group(2), mMatcher.group(3));
+ }
+
+ public Property next() throws ParseException {
+ return next(false);
+ }
+
+ public String remaining() {
+ return mStr.substring(mPos);
+ }
+
+ public int pos() {
+ return mPos + mOffset;
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/utils/ObexAppParameters.java b/src/com/android/bluetooth/pbapclient/utils/ObexAppParameters.java
new file mode 100644
index 0000000..29027ee
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/utils/ObexAppParameters.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient.utils;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.obex.HeaderSet;
+
+public final class ObexAppParameters {
+
+ private final HashMap<Byte, byte[]> mParams;
+
+ public ObexAppParameters() {
+ mParams = new HashMap<Byte, byte[]>();
+ }
+
+ public ObexAppParameters(byte[] raw) {
+ mParams = new HashMap<Byte, byte[]>();
+
+ if (raw != null) {
+ for (int i = 0; i < raw.length;) {
+ if (raw.length - i < 2) {
+ break;
+ }
+
+ byte tag = raw[i++];
+ byte len = raw[i++];
+
+ if (raw.length - i - len < 0) {
+ break;
+ }
+
+ byte[] val = new byte[len];
+
+ System.arraycopy(raw, i, val, 0, len);
+ this.add(tag, val);
+
+ i += len;
+ }
+ }
+ }
+
+ public static ObexAppParameters fromHeaderSet(HeaderSet headerset) {
+ try {
+ byte[] raw = (byte[]) headerset.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ return new ObexAppParameters(raw);
+ } catch (IOException e) {
+ // won't happen
+ }
+
+ return null;
+ }
+
+ public byte[] getHeader() {
+ int length = 0;
+
+ for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+ length += (entry.getValue().length + 2);
+ }
+
+ byte[] ret = new byte[length];
+
+ int idx = 0;
+ for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+ length = entry.getValue().length;
+
+ ret[idx++] = entry.getKey();
+ ret[idx++] = (byte) length;
+ System.arraycopy(entry.getValue(), 0, ret, idx, length);
+ idx += length;
+ }
+
+ return ret;
+ }
+
+ public void addToHeaderSet(HeaderSet headerset) {
+ if (mParams.size() > 0) {
+ headerset.setHeader(HeaderSet.APPLICATION_PARAMETER, getHeader());
+ }
+ }
+
+ public boolean exists(byte tag) {
+ return mParams.containsKey(tag);
+ }
+
+ public void add(byte tag, byte val) {
+ byte[] bval = ByteBuffer.allocate(1).put(val).array();
+ mParams.put(tag, bval);
+ }
+
+ public void add(byte tag, short val) {
+ byte[] bval = ByteBuffer.allocate(2).putShort(val).array();
+ mParams.put(tag, bval);
+ }
+
+ public void add(byte tag, int val) {
+ byte[] bval = ByteBuffer.allocate(4).putInt(val).array();
+ mParams.put(tag, bval);
+ }
+
+ public void add(byte tag, long val) {
+ byte[] bval = ByteBuffer.allocate(8).putLong(val).array();
+ mParams.put(tag, bval);
+ }
+
+ public void add(byte tag, String val) {
+ byte[] bval = val.getBytes();
+ mParams.put(tag, bval);
+ }
+
+ public void add(byte tag, byte[] bval) {
+ mParams.put(tag, bval);
+ }
+
+ public byte getByte(byte tag) {
+ byte[] bval = mParams.get(tag);
+
+ if (bval == null || bval.length < 1) {
+ return 0;
+ }
+
+ return ByteBuffer.wrap(bval).get();
+ }
+
+ public short getShort(byte tag) {
+ byte[] bval = mParams.get(tag);
+
+ if (bval == null || bval.length < 2) {
+ return 0;
+ }
+
+ return ByteBuffer.wrap(bval).getShort();
+ }
+
+ public int getInt(byte tag) {
+ byte[] bval = mParams.get(tag);
+
+ if (bval == null || bval.length < 4) {
+ return 0;
+ }
+
+ return ByteBuffer.wrap(bval).getInt();
+ }
+
+ public String getString(byte tag) {
+ byte[] bval = mParams.get(tag);
+
+ if (bval == null) {
+ return null;
+ }
+
+ return new String(bval);
+ }
+
+ public byte[] getByteArray(byte tag) {
+ byte[] bval = mParams.get(tag);
+
+ return bval;
+ }
+
+ @Override
+ public String toString() {
+ return mParams.toString();
+ }
+}
diff --git a/src/com/android/bluetooth/pbapclient/utils/ObexTime.java b/src/com/android/bluetooth/pbapclient/utils/ObexTime.java
new file mode 100644
index 0000000..d4ee4e6
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/utils/ObexTime.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.pbapclient.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class ObexTime {
+
+ private Date mDate;
+
+ public ObexTime(String time) {
+ /*
+ * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
+ * +/-hhmm
+ */
+ Pattern p = Pattern
+ .compile("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2}))?");
+ Matcher m = p.matcher(time);
+
+ if (m.matches()) {
+
+ /*
+ * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
+ * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
+ * are guaranteed to be numeric so conversion will always succeed
+ * (except group 8 which is either + or -)
+ */
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
+ Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
+ Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+
+ /*
+ * if 7th group is matched then we have UTC offset information
+ * included
+ */
+ if (m.group(7) != null) {
+ int ohh = Integer.parseInt(m.group(9));
+ int omm = Integer.parseInt(m.group(10));
+
+ /* time zone offset is specified in miliseconds */
+ int offset = (ohh * 60 + omm) * 60 * 1000;
+
+ if (m.group(8).equals("-")) {
+ offset = -offset;
+ }
+
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+ tz.setRawOffset(offset);
+
+ cal.setTimeZone(tz);
+ }
+
+ mDate = cal.getTime();
+ }
+ }
+
+ public ObexTime(Date date) {
+ mDate = date;
+ }
+
+ public Date getTime() {
+ return mDate;
+ }
+
+ @Override
+ public String toString() {
+ if (mDate == null) {
+ return null;
+ }
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(mDate);
+
+ /* note that months are numbered stating from 0 */
+ return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d",
+ cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
+ cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),
+ cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));
+ }
+}
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index 339f676..af8a900 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -30,6 +30,7 @@
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.bluetooth.BluetoothSap;
//import com.android.internal.telephony.RIL;
import com.google.protobuf.micro.CodedOutputStreamMicro;
@@ -314,7 +315,6 @@
mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
mRilBtReceiverThread = new Thread(mRilBtReceiver, "RilBtReceiver");
- setNotification(SapMessage.DISC_GRACEFULL,0);
boolean done = false;
while (!done) {
if(VERBOSE) Log.i(TAG, "Waiting for incomming RFCOMM message...");
@@ -350,16 +350,20 @@
* close socket-streams and initiate cleanup */
if(VERBOSE) Log.d(TAG, "DISCONNECT_REQ");
- clearPendingRilResponses(msg);
-
- changeState(SAP_STATE.DISCONNECTING);
-
- sendRilThreadMessage(msg);
- /* We simply need to forward to RIL, but not change state to busy
- * - hence send and set message to null. */
- msg = null; // don't send twice
- /*cancel the timer for the hard-disconnect intent*/
- stopDisconnectTimer();
+ if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+ Log.d(TAG, "disconnect received when call was ongoing, " +
+ "send disconnect response");
+ changeState(SAP_STATE.DISCONNECTING);
+ SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+ sendClientMessage(reply);
+ } else {
+ clearPendingRilResponses(msg);
+ changeState(SAP_STATE.DISCONNECTING);
+ sendRilThreadMessage(msg);
+ /*cancel the timer for the hard-disconnect intent*/
+ stopDisconnectTimer();
+ }
+ msg = null; // No message needs to be sent to RIL
break;
case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
case SapMessage.ID_RESET_SIM_REQ:
@@ -786,6 +790,14 @@
mState == SAP_STATE.DISCONNECTING) {
sapMsg = null;
}
+ if (mSapServiceHandler != null && mState == SAP_STATE.CONNECTED) {
+ Message msg = Message.obtain(mSapServiceHandler);
+ msg.what = SapService.MSG_CHANGE_STATE;
+ msg.arg1 = BluetoothSap.STATE_CONNECTED;
+ msg.sendToTarget();
+ setNotification(SapMessage.DISC_GRACEFULL, 0);
+ if (DEBUG) Log.d(TAG, "MSG_CHANGE_STATE sent out.");
+ }
break;
default:
// Nothing special, just send the message
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 23d69ff..4aa3f65 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -60,6 +60,8 @@
public static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
public static final int MSG_RELEASE_WAKE_LOCK = 5006;
+ public static final int MSG_CHANGE_STATE = 5007;
+
/* Each time a transaction between the SIM and the BT Client is detected a wakelock is taken.
* After an idle period of RELEASE_WAKE_LOCK_DELAY ms the wakelock is released.
*
@@ -90,6 +92,7 @@
private boolean mRemoveTimeoutMsg = false;
private boolean mIsWaitingAuthorization = false;
+ private boolean mIsRegistered = false;
// package and class name to which we send intent to check message access access permission
private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
@@ -119,7 +122,14 @@
}
}
-
+ private void removeSdpRecord() {
+ if (mAdapter != null && mSdpHandle >= 0 &&
+ SdpManager.getDefaultManager() != null) {
+ if(VERBOSE) Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
+ boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
+ mSdpHandle = -1;
+ }
+ }
private void startRfcommSocketListener() {
if (VERBOSE) Log.v(TAG, "Sap Service startRfcommSocketListener");
@@ -146,10 +156,7 @@
// for multiple connections.
mServerSocket = mAdapter.listenUsingRfcommOn(
BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
- if (mSdpHandle >= 0) {
- SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
- if (VERBOSE) Log.d(TAG, "Removing SDP record");
- }
+ removeSdpRecord();
mSdpHandle = SdpManager.getDefaultManager().createSapsRecord(SDP_SAP_SERVICE_NAME,
mServerSocket.getChannel(), SDP_SAP_VERSION);
} catch (IOException e) {
@@ -176,11 +183,6 @@
break;
}
}
- if (mInterrupted) {
- initSocketOK = false;
- // close server socket to avoid resource leakage
- closeServerSocket();
- }
if (initSocketOK) {
if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
@@ -255,8 +257,6 @@
mWakeLock.acquire();
}
- setState(BluetoothSap.STATE_CONNECTED);
-
/* Start the SAP I/O thread and associate with message handler */
mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(), mConnSocket.getOutputStream());
mSapServer.start();
@@ -440,6 +440,10 @@
if (DEBUG) Log.i(TAG, " Released Wake Lock by message");
}
break;
+ case MSG_CHANGE_STATE:
+ if (DEBUG) Log.d(TAG, "change state message: newState = " + msg.arg1);
+ setState(msg.arg1);
+ break;
case SHUTDOWN:
/* Ensure to call close from this handler to avoid starting new stuff
because of pending messages */
@@ -576,6 +580,7 @@
try {
registerReceiver(mSapReceiver, filter);
+ mIsRegistered = true;
} catch (Exception e) {
Log.w(TAG,"Unable to register sap receiver",e);
}
@@ -590,7 +595,12 @@
@Override
protected boolean stop() {
Log.v(TAG, "stop()");
+ if (!mIsRegistered){
+ Log.i(TAG, "Avoid unregister when receiver it is not registered");
+ return true;
+ }
try {
+ mIsRegistered = false;
unregisterReceiver(mSapReceiver);
} catch (Exception e) {
Log.w(TAG,"Unable to unregister sap receiver",e);
@@ -652,6 +662,7 @@
mIsWaitingAuthorization = false;
cancelUserTimeoutAlarm();
}
+ removeSdpRecord();
mSessionStatusHandler.removeCallbacksAndMessages(null);
// Request release of all resources
mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
@@ -741,13 +752,15 @@
if (DEBUG) Log.d(TAG,"ACL disconnected for " + device);
- if (mRemoteDevice.equals(device) && mRemoveTimeoutMsg) {
- // Send any pending timeout now, as ACL got disconnected.
- cancelUserTimeoutAlarm();
- mSessionStatusHandler.removeMessages(USER_TIMEOUT);
- sendCancelUserConfirmationIntent(mRemoteDevice);
+ if (mRemoteDevice.equals(device)) {
+ if (mRemoveTimeoutMsg) {
+ // Send any pending timeout now, as ACL got disconnected.
+ cancelUserTimeoutAlarm();
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ sendCancelUserConfirmationIntent(mRemoteDevice);
+ }
mIsWaitingAuthorization = false;
- mRemoveTimeoutMsg = false;
+ setState(BluetoothSap.STATE_DISCONNECTED);
// Ensure proper cleanup, and prepare for new connect.
mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
}
diff --git a/tests/src/com/android/bluetooth/tests/pbap/BluetoothPhabVcardManagerTest.java b/tests/src/com/android/bluetooth/tests/pbap/BluetoothPbapVcardManagerTest.java
similarity index 86%
rename from tests/src/com/android/bluetooth/tests/pbap/BluetoothPhabVcardManagerTest.java
rename to tests/src/com/android/bluetooth/tests/pbap/BluetoothPbapVcardManagerTest.java
index 72168b5..ec000a0 100644
--- a/tests/src/com/android/bluetooth/tests/pbap/BluetoothPhabVcardManagerTest.java
+++ b/tests/src/com/android/bluetooth/tests/pbap/BluetoothPbapVcardManagerTest.java
@@ -16,26 +16,43 @@
package com.android.bluetooth.tests.pbap;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.provider.ContactsContract;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
-import android.test.mock.MockContentResolver;
-
import com.android.bluetooth.pbap.BluetoothPbapObexServer;
import com.android.bluetooth.pbap.BluetoothPbapVcardManager;
import com.android.bluetooth.tests.mock.BluetoothMockContext;
import com.android.bluetooth.tests.mock.SimpleMockContentProvider;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.PhoneLookup;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.TextUtils;
+
+
import java.util.ArrayList;
-public class BluetoothPhabVcardManagerTest extends AndroidTestCase {
+public class BluetoothPbapVcardManagerTest extends AndroidTestCase {
- public void testGetContactNamesByNumber() {
- MatrixCursor mc = new MatrixCursor(
- new String[]{ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME});
+ public void testGetContactNamesByNumberWithEmptyPhoneNumber() {
+ getContactNamesByNumberInternal("");
+ }
+
+ public void testGetContactNamesByNumberWithPhoneNumber() {
+ getContactNamesByNumberInternal("111-111-111");
+ }
+
+ private void getContactNamesByNumberInternal(String phoneNumber) {
+ String[] columnNames;
+ if (TextUtils.isEmpty(phoneNumber)) {
+ columnNames = new String[]{Phone.CONTACT_ID, Phone.DISPLAY_NAME};
+ } else {
+ columnNames = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
+ }
+
+ MatrixCursor mc = new MatrixCursor(columnNames);
mc.addRow(new Object[]{1L, "A"});
mc.addRow(new Object[]{1L, "A (1)"});
mc.addRow(new Object[]{2L, "B"});
@@ -45,7 +62,7 @@
mc.addRow(new Object[]{3L, "C (2)"});
mc.addRow(new Object[]{4L, "D"});
BluetoothPbapVcardManager manager = createBluetoothPbapVcardManager(mc);
- ArrayList<String> nameList = manager.getContactNamesByNumber("111-111-111");
+ ArrayList<String> nameList = manager.getContactNamesByNumber(phoneNumber);
// If there are multiple display name per id, first one is picked.
assertEquals("A,1", nameList.get(0));