diff --git a/AndroidTestTemplate.xml b/AndroidTestTemplate.xml
index 4fb4bf9f76c1ee696673292530aa57f9e439146d..8332422b344895846238a5780915bfb73eb23fbf 100644
--- a/AndroidTestTemplate.xml
+++ b/AndroidTestTemplate.xml
@@ -24,7 +24,8 @@
-
+
+
@@ -38,6 +39,7 @@
diff --git a/TEST_MAPPING b/TEST_MAPPING
old mode 100755
new mode 100644
index 69a0709acb61c1dcbddbfd49052dbfbc469a1d49..65f2fefd19eeb4efa4c28a7848210d4673a427c9
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -62,6 +62,12 @@
{
"name" : "net_test_btif_hf_client_service"
},
+ {
+ "name" : "libaptx_enc_tests"
+ },
+ {
+ "name" : "libaptxhd_enc_tests"
+ },
{
"name" : "net_test_stack_btm"
}
diff --git a/android/BluetoothLegacyMigration/Android.bp b/android/BluetoothLegacyMigration/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..b755fa0895a43b0329b1aa30c099bf00e4417c43
--- /dev/null
+++ b/android/BluetoothLegacyMigration/Android.bp
@@ -0,0 +1,14 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "BluetoothLegacyMigration",
+
+ srcs: [ "BluetoothLegacyMigration.kt" ],
+
+ // Must match Bluetooth.apk certificate because of sharedUserId
+ certificate: ":com.android.bluetooth.certificate",
+ platform_apis: true,
+}
diff --git a/android/BluetoothLegacyMigration/AndroidManifest.xml b/android/BluetoothLegacyMigration/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..86989f9b0a19a9cb3eb3e1f7d82c84f1dfcccb72
--- /dev/null
+++ b/android/BluetoothLegacyMigration/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt b/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9c88a061385bf831329be043a13a2c31225c7d67
--- /dev/null
+++ b/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2022 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.bluetooth
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+
+/**
+ * Define an implementation of ContentProvider for the Bluetooth migration
+ */
+class BluetoothLegacyMigration: ContentProvider() {
+ companion object {
+ private const val TAG = "BluetoothLegacyMigration"
+
+ private const val AUTHORITY = "bluetooth_legacy.provider"
+
+ private const val START_LEGACY_MIGRATION_CALL = "start_legacy_migration"
+ private const val FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration"
+
+ private const val PHONEBOOK_ACCESS_PERMISSION = "phonebook_access_permission"
+ private const val MESSAGE_ACCESS_PERMISSION = "message_access_permission"
+ private const val SIM_ACCESS_PERMISSION = "sim_access_permission"
+
+ private const val VOLUME_MAP = "bluetooth_volume_map"
+
+ private const val OPP = "OPPMGR"
+ private const val BLUETOOTH_OPP_CHANNEL = "btopp_channels"
+ private const val BLUETOOTH_OPP_NAME = "btopp_names"
+
+ private const val BLUETOOTH_SIGNED_DEFAULT = "com.google.android.bluetooth_preferences"
+
+ private const val KEY_LIST = "key_list"
+
+ private enum class UriId(
+ val fileName: String,
+ val handler: (ctx: Context) -> DatabaseHandler
+ ) {
+ BLUETOOTH(BluetoothDatabase.DATABASE_NAME, ::BluetoothDatabase),
+ OPP(OppDatabase.DATABASE_NAME, ::OppDatabase),
+ }
+
+ private val URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH).apply {
+ UriId.values().map { addURI(AUTHORITY, it.fileName, it.ordinal) }
+ }
+
+ private fun putObjectInBundle(bundle: Bundle, key: String, obj: Any?) {
+ when (obj) {
+ is Boolean -> bundle.putBoolean(key, obj)
+ is Int -> bundle.putInt(key, obj)
+ is Long -> bundle.putLong(key, obj)
+ is String -> bundle.putString(key, obj)
+ null -> throw UnsupportedOperationException("null type is not handled")
+ else -> throw UnsupportedOperationException("${obj.javaClass.simpleName}: type is not handled")
+ }
+ }
+ }
+
+ private lateinit var mContext: Context
+
+ /**
+ * Always return true, indicating that the
+ * provider loaded correctly.
+ */
+ override fun onCreate(): Boolean {
+ mContext = context!!.createDeviceProtectedStorageContext()
+ return true
+ }
+
+ /**
+ * Use a content URI to get database name associated
+ *
+ * @param uri Content uri
+ * @return A {@link Cursor} containing the results of the query.
+ */
+ override fun getType(uri: Uri): String {
+ val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
+ ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
+ return database.fileName
+ }
+
+ /**
+ * Use a content URI to get information about a database
+ *
+ * @param uri Content uri
+ * @param projection unused
+ * @param selection unused
+ * @param selectionArgs unused
+ * @param sortOrder unused
+ * @return A {@link Cursor} containing the results of the query.
+ *
+ */
+ @Override
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? {
+ val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
+ ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
+ return database.handler(mContext).toCursor()
+ }
+
+ /**
+ * insert() is not supported
+ */
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException()
+ }
+
+ /**
+ * delete() is not supported
+ */
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ /**
+ * update() is not supported
+ */
+ override fun update(
+ uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?
+ ): Int {
+ throw UnsupportedOperationException()
+ }
+
+ abstract class MigrationHandler {
+ abstract fun toBundle(): Bundle?
+ abstract fun delete()
+ }
+
+ private class SharedPreferencesHandler(private val ctx: Context, private val key: String) :
+ MigrationHandler() {
+
+ override fun toBundle(): Bundle? {
+ val pref = ctx.getSharedPreferences(key, Context.MODE_PRIVATE)
+ if (pref.all.isEmpty()) {
+ Log.d(TAG, "No migration needed for shared preference: $key")
+ return null
+ }
+ val bundle = Bundle()
+ val keys = arrayListOf()
+ for (e in pref.all) {
+ keys += e.key
+ putObjectInBundle(bundle, e.key, e.value)
+ }
+ bundle.putStringArrayList(KEY_LIST, keys)
+ Log.d(TAG, "SharedPreferences migrating ${keys.size} key(s) from $key")
+ return bundle
+ }
+
+ override fun delete() {
+ ctx.deleteSharedPreferences(key)
+ Log.d(TAG, "$key: SharedPreferences deleted")
+ }
+ }
+
+ abstract class DatabaseHandler(private val ctx: Context, private val dbName: String) :
+ MigrationHandler() {
+
+ abstract val sql: String
+
+ fun toCursor(): Cursor? {
+ val databasePath = ctx.getDatabasePath(dbName)
+ if (!databasePath.exists()) {
+ Log.d(TAG, "No migration needed for database: $dbName")
+ return null
+ }
+ val db = SQLiteDatabase.openDatabase(
+ databasePath,
+ SQLiteDatabase.OpenParams.Builder().addOpenFlags(SQLiteDatabase.OPEN_READONLY)
+ .build()
+ )
+ return db.rawQuery(sql, null)
+ }
+
+ override fun toBundle(): Bundle? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun delete() {
+ val databasePath = ctx.getDatabasePath(dbName)
+ databasePath.delete()
+ Log.d(TAG, "$dbName: database deleted")
+ }
+ }
+
+ private class BluetoothDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
+ companion object {
+ const val DATABASE_NAME = "bluetooth_db"
+ }
+ private val dbTable = "metadata"
+ override val sql = "select * from $dbTable"
+ }
+
+ private class OppDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
+ companion object {
+ const val DATABASE_NAME = "btopp.db"
+ }
+ private val dbTable = "btopp"
+ override val sql = "select * from $dbTable"
+ }
+
+ /**
+ * Fetch legacy data describe by {@code arg} and perform {@code method} action on it
+ *
+ * @param method Action to perform. One of START_LEGACY_MIGRATION_CALL|FINISH_LEGACY_MIGRATION_CALL
+ * @param arg item on witch to perform the action specified by {@code method}
+ * @param extras unused
+ * @return A {@link Bundle} containing the results of the query.
+ */
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+ val migrationHandler = when (arg) {
+ OPP,
+ VOLUME_MAP,
+ BLUETOOTH_OPP_NAME,
+ BLUETOOTH_OPP_CHANNEL,
+ SIM_ACCESS_PERMISSION,
+ MESSAGE_ACCESS_PERMISSION,
+ PHONEBOOK_ACCESS_PERMISSION -> SharedPreferencesHandler(mContext, arg)
+ BLUETOOTH_SIGNED_DEFAULT -> {
+ val key = mContext.packageName + "_preferences"
+ SharedPreferencesHandler(mContext, key)
+ }
+ BluetoothDatabase.DATABASE_NAME -> BluetoothDatabase(mContext)
+ OppDatabase.DATABASE_NAME -> OppDatabase(mContext)
+ else -> throw UnsupportedOperationException()
+ }
+ return when (method) {
+ START_LEGACY_MIGRATION_CALL -> migrationHandler.toBundle()
+ FINISH_LEGACY_MIGRATION_CALL -> {
+ migrationHandler.delete()
+ return null
+ }
+ else -> throw UnsupportedOperationException()
+ }
+ }
+}
diff --git a/android/BluetoothLegacyMigration/OWNERS b/android/BluetoothLegacyMigration/OWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..8f5c903d7e8759d22a59ff162a43fd73a6309d16
--- /dev/null
+++ b/android/BluetoothLegacyMigration/OWNERS
@@ -0,0 +1,8 @@
+# Reviewers for /android/BluetoothLegacyMigration
+
+eruffieux@google.com
+rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
+wescande@google.com
+zachoverflow@google.com
diff --git a/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml b/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ff00b0d77b333e0fe27bfcb8eceeba52219f80a3
--- /dev/null
+++ b/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/app/Android.bp b/android/app/Android.bp
index 90f39fd6b09340b26b9a90324d16123c55493836..2e71dccc464fbf4186ac9ae2ef3f8a9ea30e7b9a 100644
--- a/android/app/Android.bp
+++ b/android/app/Android.bp
@@ -69,12 +69,6 @@ cc_library_shared {
"libbluetooth",
"libc++fs",
],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wextra",
- "-Wno-unused-parameter",
- ],
sanitize: {
scs: true,
},
@@ -114,7 +108,7 @@ android_app {
static_libs: [
"android.hardware.radio-V1.0-java",
"androidx.core_core",
- "androidx.legacy_legacy-support-v4",
+ "androidx.media_media",
"androidx.lifecycle_lifecycle-livedata",
"androidx.room_room-runtime",
"androidx.annotation_annotation",
diff --git a/android/app/AndroidManifest.xml b/android/app/AndroidManifest.xml
index 8ed18e2794f537d64e17000eb95616287df40892..9484744656a238d463298df6e6ae75ee525dec9c 100644
--- a/android/app/AndroidManifest.xml
+++ b/android/app/AndroidManifest.xml
@@ -76,6 +76,7 @@
+
diff --git a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
index ba69e740aca8c3abec353312ed8e43e2c13b974f..f25e208d977c2e8c79c101f5bd961f880a1fe41c 100644
--- a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -1810,6 +1810,35 @@ static jboolean allowLowLatencyAudioNative(JNIEnv* env, jobject obj,
return true;
}
+static void metadataChangedNative(JNIEnv* env, jobject obj, jbyteArray address,
+ jint key, jbyteArray value) {
+ ALOGV("%s", __func__);
+ if (!sBluetoothInterface) return;
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (addr == nullptr) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+ RawAddress addr_obj = {};
+ addr_obj.FromOctets((uint8_t*)addr);
+
+ if (value == NULL) {
+ ALOGE("metadataChangedNative() ignoring NULL array");
+ return;
+ }
+
+ uint16_t len = (uint16_t)env->GetArrayLength(value);
+ jbyte* p_value = env->GetByteArrayElements(value, NULL);
+ if (p_value == NULL) return;
+
+ std::vector val_vec(reinterpret_cast(p_value),
+ reinterpret_cast(p_value + len));
+ env->ReleaseByteArrayElements(value, p_value, 0);
+
+ sBluetoothInterface->metadata_changed(addr_obj, key, std::move(val_vec));
+ return;
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
@@ -1852,6 +1881,7 @@ static JNINativeMethod sMethods[] = {
{"requestMaximumTxDataLengthNative", "([B)V",
(void*)requestMaximumTxDataLengthNative},
{"allowLowLatencyAudioNative", "(Z[B)Z", (void*)allowLowLatencyAudioNative},
+ {"metadataChangedNative", "([BI[B)V", (void*)metadataChangedNative},
};
int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) {
diff --git a/android/app/jni/com_android_bluetooth_gatt.cpp b/android/app/jni/com_android_bluetooth_gatt.cpp
index 963eb56f7492a6781b3cae27f81c19fff646eed5..03517fc645eba3800b4585cdd92e95987c22a4da 100644
--- a/android/app/jni/com_android_bluetooth_gatt.cpp
+++ b/android/app/jni/com_android_bluetooth_gatt.cpp
@@ -199,8 +199,9 @@ static std::shared_mutex callbacks_mutex;
*/
void btgattc_register_app_cb(int status, int clientIf, const Uuid& app_uuid) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
clientIf, UUID_PARAMS(app_uuid));
}
@@ -212,8 +213,9 @@ void btgattc_scan_result_cb(uint16_t event_type, uint8_t addr_type,
uint16_t periodic_adv_int,
std::vector adv_data,
RawAddress* original_bda) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), bda));
@@ -233,8 +235,9 @@ void btgattc_scan_result_cb(uint16_t event_type, uint8_t addr_type,
void btgattc_open_cb(int conn_id, int status, int clientIf,
const RawAddress& bda) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -244,8 +247,9 @@ void btgattc_open_cb(int conn_id, int status, int clientIf,
void btgattc_close_cb(int conn_id, int status, int clientIf,
const RawAddress& bda) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -254,8 +258,9 @@ void btgattc_close_cb(int conn_id, int status, int clientIf,
}
void btgattc_search_complete_cb(int conn_id, int status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSearchCompleted, conn_id,
status);
@@ -263,16 +268,18 @@ void btgattc_search_complete_cb(int conn_id, int status) {
void btgattc_register_for_notification_cb(int conn_id, int registered,
int status, uint16_t handle) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRegisterForNotifications,
conn_id, status, registered, handle);
}
void btgattc_notify_cb(int conn_id, const btgatt_notify_params_t& p_data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(
sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &p_data.bda));
@@ -288,8 +295,9 @@ void btgattc_notify_cb(int conn_id, const btgatt_notify_params_t& p_data) {
void btgattc_read_characteristic_cb(int conn_id, int status,
btgatt_read_params_t* p_data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(), NULL);
if (status == 0) { // Success
@@ -308,8 +316,9 @@ void btgattc_read_characteristic_cb(int conn_id, int status,
void btgattc_write_characteristic_cb(int conn_id, int status, uint16_t handle,
uint16_t len, const uint8_t* value) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(), NULL);
jb.reset(sCallbackEnv->NewByteArray(len));
@@ -319,8 +328,9 @@ void btgattc_write_characteristic_cb(int conn_id, int status, uint16_t handle,
}
void btgattc_execute_write_cb(int conn_id, int status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onExecuteCompleted,
conn_id, status);
@@ -328,8 +338,9 @@ void btgattc_execute_write_cb(int conn_id, int status) {
void btgattc_read_descriptor_cb(int conn_id, int status,
const btgatt_read_params_t& p_data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(), NULL);
if (p_data.value.len != 0) {
@@ -346,8 +357,9 @@ void btgattc_read_descriptor_cb(int conn_id, int status,
void btgattc_write_descriptor_cb(int conn_id, int status, uint16_t handle,
uint16_t len, const uint8_t* value) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(), NULL);
jb.reset(sCallbackEnv->NewByteArray(len));
@@ -358,8 +370,9 @@ void btgattc_write_descriptor_cb(int conn_id, int status, uint16_t handle,
void btgattc_remote_rssi_cb(int client_if, const RawAddress& bda, int rssi,
int status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -369,23 +382,26 @@ void btgattc_remote_rssi_cb(int client_if, const RawAddress& bda, int rssi,
}
void btgattc_configure_mtu_cb(int conn_id, int status, int mtu) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConfigureMTU, conn_id,
status, mtu);
}
void btgattc_congestion_cb(int conn_id, bool congested) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientCongestion,
conn_id, congested);
}
void btgattc_batchscan_reports_cb(int client_if, int status, int report_format,
int num_records, std::vector data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(data.size()));
sCallbackEnv->SetByteArrayRegion(jb.get(), 0, data.size(),
@@ -396,15 +412,17 @@ void btgattc_batchscan_reports_cb(int client_if, int status, int report_format,
}
void btgattc_batchscan_threshold_cb(int client_if) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj,
method_onBatchScanThresholdCrossed, client_if);
}
void btgattc_track_adv_event_cb(btgatt_track_adv_info_t* p_adv_track_info) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(
sCallbackEnv.get(),
@@ -506,8 +524,9 @@ void fillGattDbElementArray(JNIEnv* env, jobject* array,
void btgattc_get_gatt_db_cb(int conn_id, const btgatt_db_element_t* db,
int count) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
jclass arrayListclazz = sCallbackEnv->FindClass("java/util/ArrayList");
ScopedLocalRef array(
@@ -525,8 +544,9 @@ void btgattc_get_gatt_db_cb(int conn_id, const btgatt_db_element_t* db,
void btgattc_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientPhyUpdate, conn_id,
tx_phy, rx_phy, status);
@@ -534,16 +554,18 @@ void btgattc_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
void btgattc_conn_updated_cb(int conn_id, uint16_t interval, uint16_t latency,
uint16_t timeout, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientConnUpdate,
conn_id, interval, latency, timeout, status);
}
void btgattc_service_changed_cb(int conn_id) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceChanged, conn_id);
}
@@ -583,16 +605,18 @@ static const btgatt_client_callbacks_t sGattClientCallbacks = {
*/
void btgatts_register_app_cb(int status, int server_if, const Uuid& uuid) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerRegistered, status,
server_if, UUID_PARAMS(uuid));
}
void btgatts_connection_cb(int conn_id, int server_if, int connected,
const RawAddress& bda) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -603,8 +627,9 @@ void btgatts_connection_cb(int conn_id, int server_if, int connected,
void btgatts_service_added_cb(int status, int server_if,
const btgatt_db_element_t* service,
size_t service_count) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
jclass arrayListclazz = sCallbackEnv->FindClass("java/util/ArrayList");
ScopedLocalRef array(
@@ -620,15 +645,17 @@ void btgatts_service_added_cb(int status, int server_if,
}
void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceStopped, status,
server_if, srvc_handle);
}
void btgatts_service_deleted_cb(int status, int server_if, int srvc_handle) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDeleted, status,
server_if, srvc_handle);
}
@@ -637,8 +664,9 @@ void btgatts_request_read_characteristic_cb(int conn_id, int trans_id,
const RawAddress& bda,
int attr_handle, int offset,
bool is_long) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -650,8 +678,9 @@ void btgatts_request_read_characteristic_cb(int conn_id, int trans_id,
void btgatts_request_read_descriptor_cb(int conn_id, int trans_id,
const RawAddress& bda, int attr_handle,
int offset, bool is_long) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -666,8 +695,9 @@ void btgatts_request_write_characteristic_cb(int conn_id, int trans_id,
bool need_rsp, bool is_prep,
const uint8_t* value,
size_t length) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -685,8 +715,9 @@ void btgatts_request_write_descriptor_cb(int conn_id, int trans_id,
int offset, bool need_rsp,
bool is_prep, const uint8_t* value,
size_t length) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -701,8 +732,9 @@ void btgatts_request_write_descriptor_cb(int conn_id, int trans_id,
void btgatts_request_exec_write_cb(int conn_id, int trans_id,
const RawAddress& bda, int exec_write) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -711,37 +743,42 @@ void btgatts_request_exec_write_cb(int conn_id, int trans_id,
}
void btgatts_response_confirmation_cb(int status, int handle) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onResponseSendCompleted,
status, handle);
}
void btgatts_indication_sent_cb(int conn_id, int status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNotificationSent,
conn_id, status);
}
void btgatts_congestion_cb(int conn_id, bool congested) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerCongestion,
conn_id, congested);
}
void btgatts_mtu_changed_cb(int conn_id, int mtu) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerMtuChanged,
conn_id, mtu);
}
void btgatts_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerPhyUpdate, conn_id,
tx_phy, rx_phy, status);
@@ -749,8 +786,9 @@ void btgatts_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
void btgatts_conn_updated_cb(int conn_id, uint16_t interval, uint16_t latency,
uint16_t timeout, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerConnUpdate,
conn_id, interval, latency, timeout, status);
@@ -890,15 +928,17 @@ class JniScanningCallbacks : ScanningCallbacks {
void OnScannerRegistered(const Uuid app_uuid, uint8_t scannerId,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
status, scannerId, UUID_PARAMS(app_uuid));
}
void OnSetScannerParameterComplete(uint8_t scannerId, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(
mCallbacksObj, method_onScanParamSetupCompleted, status, scannerId);
}
@@ -907,8 +947,9 @@ class JniScanningCallbacks : ScanningCallbacks {
uint8_t primary_phy, uint8_t secondary_phy,
uint8_t advertising_sid, int8_t tx_power, int8_t rssi,
uint16_t periodic_adv_int, std::vector adv_data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -932,8 +973,9 @@ class JniScanningCallbacks : ScanningCallbacks {
}
void OnTrackAdvFoundLost(AdvertisingTrackInfo track_info) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(
sCallbackEnv.get(),
@@ -973,8 +1015,9 @@ class JniScanningCallbacks : ScanningCallbacks {
void OnBatchScanReports(int client_if, int status, int report_format,
int num_records, std::vector data) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(data.size()));
sCallbackEnv->SetByteArrayRegion(jb.get(), 0, data.size(),
@@ -986,8 +1029,9 @@ class JniScanningCallbacks : ScanningCallbacks {
}
void OnBatchScanThresholdCrossed(int client_if) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj,
method_onBatchScanThresholdCrossed, client_if);
}
@@ -996,6 +1040,7 @@ class JniScanningCallbacks : ScanningCallbacks {
uint8_t sid, uint8_t address_type,
RawAddress address, uint8_t phy,
uint16_t interval) override {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mPeriodicScanCallbacksObj) {
@@ -1013,8 +1058,9 @@ class JniScanningCallbacks : ScanningCallbacks {
void OnPeriodicSyncReport(uint16_t sync_handle, int8_t tx_power, int8_t rssi,
uint8_t data_status,
std::vector data) override {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mPeriodicScanCallbacksObj) return;
ScopedLocalRef jb(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(data.size()));
@@ -1027,8 +1073,9 @@ class JniScanningCallbacks : ScanningCallbacks {
}
void OnPeriodicSyncLost(uint16_t sync_handle) override {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mPeriodicScanCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncLost,
sync_handle);
@@ -1036,6 +1083,7 @@ class JniScanningCallbacks : ScanningCallbacks {
void OnPeriodicSyncTransferred(int pa_source, uint8_t status,
RawAddress address) override {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mPeriodicScanCallbacksObj) {
@@ -1168,6 +1216,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
static const bt_interface_t* btIf;
static void initializeNative(JNIEnv* env, jobject object) {
+ std::unique_lock lock(callbacks_mutex);
if (btIf) return;
btIf = getBluetoothInterface();
@@ -1210,6 +1259,8 @@ static void initializeNative(JNIEnv* env, jobject object) {
}
static void cleanupNative(JNIEnv* env, jobject object) {
+ std::unique_lock lock(callbacks_mutex);
+
if (!btIf) return;
if (sGattIf != NULL) {
@@ -1250,8 +1301,9 @@ static void gattClientUnregisterAppNative(JNIEnv* env, jobject object,
void btgattc_register_scanner_cb(const Uuid& app_uuid, uint8_t scannerId,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
status, scannerId, UUID_PARAMS(app_uuid));
}
@@ -1305,8 +1357,9 @@ static void gattClientSetPreferredPhyNative(JNIEnv* env, jobject object,
static void readClientPhyCb(uint8_t clientIf, RawAddress bda, uint8_t tx_phy,
uint8_t rx_phy, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -1451,8 +1504,9 @@ static void gattClientReadRemoteRssiNative(JNIEnv* env, jobject object,
}
void set_scan_params_cmpl_cb(int client_if, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanParamSetupCompleted,
status, client_if);
}
@@ -1468,8 +1522,9 @@ static void gattSetScanParametersNative(JNIEnv* env, jobject object,
void scan_filter_param_cb(uint8_t client_if, uint8_t avbl_space, uint8_t action,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj,
method_onScanFilterParamsConfigured, action,
status, client_if, avbl_space);
@@ -1547,8 +1602,9 @@ static void gattClientScanFilterParamClearAllNative(JNIEnv* env, jobject object,
static void scan_filter_cfg_cb(uint8_t client_if, uint8_t filt_type,
uint8_t avbl_space, uint8_t action,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterConfig, action,
status, client_if, filt_type, avbl_space);
}
@@ -1587,6 +1643,7 @@ static void gattClientScanFilterAddNative(JNIEnv* env, jobject object,
jfieldID nameFid = env->GetFieldID(entryClazz, "name", "Ljava/lang/String;");
jfieldID companyFid = env->GetFieldID(entryClazz, "company", "I");
jfieldID companyMaskFid = env->GetFieldID(entryClazz, "company_mask", "I");
+ jfieldID adTypeFid = env->GetFieldID(entryClazz, "ad_type", "I");
jfieldID dataFid = env->GetFieldID(entryClazz, "data", "[B");
jfieldID dataMaskFid = env->GetFieldID(entryClazz, "data_mask", "[B");
@@ -1657,6 +1714,8 @@ static void gattClientScanFilterAddNative(JNIEnv* env, jobject object,
curr.company_mask = env->GetIntField(current.get(), companyMaskFid);
+ curr.ad_type = env->GetByteField(current.get(), adTypeFid);
+
ScopedLocalRef data(
env, (jbyteArray)env->GetObjectField(current.get(), dataFid));
if (data.get() != NULL) {
@@ -1694,8 +1753,9 @@ static void gattClientScanFilterClearNative(JNIEnv* env, jobject object,
}
void scan_enable_cb(uint8_t client_if, uint8_t action, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterEnableDisabled,
action, status, client_if);
}
@@ -1726,8 +1786,9 @@ static void gattConnectionParameterUpdateNative(JNIEnv* env, jobject object,
}
void batchscan_cfg_storage_cb(uint8_t client_if, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(
mCallbacksObj, method_onBatchScanStorageConfigured, status, client_if);
}
@@ -1743,8 +1804,9 @@ static void gattClientConfigBatchScanStorageNative(
}
void batchscan_enable_cb(uint8_t client_if, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBatchScanStartStopped,
0 /* unused */, status, client_if);
}
@@ -1817,8 +1879,9 @@ static void gattServerSetPreferredPhyNative(JNIEnv* env, jobject object,
static void readServerPhyCb(uint8_t serverIf, RawAddress bda, uint8_t tx_phy,
uint8_t rx_phy, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -1961,7 +2024,6 @@ static void gattServerSendResponseNative(JNIEnv* env, jobject object,
if (env->GetArrayLength(val) < BTGATT_MAX_ATTR_LEN) {
response.attr_value.len = (uint16_t)env->GetArrayLength(val);
} else {
- android_errorWriteLog(0x534e4554, "78787521");
response.attr_value.len = BTGATT_MAX_ATTR_LEN;
}
@@ -1997,7 +2059,7 @@ static void advertiseClassInitNative(JNIEnv* env, jclass clazz) {
}
static void advertiseInitializeNative(JNIEnv* env, jobject object) {
- std::shared_lock lock(callbacks_mutex);
+ std::unique_lock lock(callbacks_mutex);
if (mAdvertiseCallbacksObj != NULL) {
ALOGW("Cleaning up Advertise callback object");
env->DeleteGlobalRef(mAdvertiseCallbacksObj);
@@ -2008,7 +2070,7 @@ static void advertiseInitializeNative(JNIEnv* env, jobject object) {
}
static void advertiseCleanupNative(JNIEnv* env, jobject object) {
- std::shared_lock lock(callbacks_mutex);
+ std::unique_lock lock(callbacks_mutex);
if (mAdvertiseCallbacksObj != NULL) {
env->DeleteGlobalRef(mAdvertiseCallbacksObj);
mAdvertiseCallbacksObj = NULL;
@@ -2097,8 +2159,9 @@ static PeriodicAdvertisingParameters parsePeriodicParams(JNIEnv* env,
static void ble_advertising_set_started_cb(int reg_id, uint8_t advertiser_id,
int8_t tx_power, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingSetStarted, reg_id,
advertiser_id, tx_power, status);
@@ -2106,8 +2169,9 @@ static void ble_advertising_set_started_cb(int reg_id, uint8_t advertiser_id,
static void ble_advertising_set_timeout_cb(uint8_t advertiser_id,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingEnabled, advertiser_id,
false, status);
@@ -2157,8 +2221,9 @@ static void stopAdvertisingSetNative(JNIEnv* env, jobject object,
static void getOwnAddressCb(uint8_t advertiser_id, uint8_t address_type,
RawAddress address) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
ScopedLocalRef addr(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &address));
@@ -2175,15 +2240,17 @@ static void getOwnAddressNative(JNIEnv* env, jobject object,
static void callJniCallback(jmethodID method, uint8_t advertiser_id,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj, method, advertiser_id,
status);
}
static void enableSetCb(uint8_t advertiser_id, bool enable, uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingEnabled, advertiser_id,
enable, status);
@@ -2221,8 +2288,9 @@ static void setScanResponseDataNative(JNIEnv* env, jobject object,
static void setAdvertisingParametersNativeCb(uint8_t advertiser_id,
uint8_t status, int8_t tx_power) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingParametersUpdated,
advertiser_id, tx_power, status);
@@ -2265,8 +2333,9 @@ static void setPeriodicAdvertisingDataNative(JNIEnv* env, jobject object,
static void enablePeriodicSetCb(uint8_t advertiser_id, bool enable,
uint8_t status) {
+ std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
+ if (!sCallbackEnv.valid() || !mAdvertiseCallbacksObj) return;
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onPeriodicAdvertisingEnabled,
advertiser_id, enable, status);
@@ -2292,6 +2361,7 @@ static void periodicScanClassInitNative(JNIEnv* env, jclass clazz) {
}
static void periodicScanInitializeNative(JNIEnv* env, jobject object) {
+ std::unique_lock lock(callbacks_mutex);
if (mPeriodicScanCallbacksObj != NULL) {
ALOGW("Cleaning up periodic scan callback object");
env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
@@ -2302,6 +2372,7 @@ static void periodicScanInitializeNative(JNIEnv* env, jobject object) {
}
static void periodicScanCleanupNative(JNIEnv* env, jobject object) {
+ std::unique_lock lock(callbacks_mutex);
if (mPeriodicScanCallbacksObj != NULL) {
env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
mPeriodicScanCallbacksObj = NULL;
diff --git a/android/app/jni/com_android_bluetooth_hfp.cpp b/android/app/jni/com_android_bluetooth_hfp.cpp
index fffaf8ccb48d014195a9f289c5959296ce2a9808..61be15f3d0243f1897aef6f2462c949b4c7f99c5 100644
--- a/android/app/jni/com_android_bluetooth_hfp.cpp
+++ b/android/app/jni/com_android_bluetooth_hfp.cpp
@@ -181,7 +181,6 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
char null_str[] = "";
if (!sCallbackEnv.isValidUtf(number)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: number is not a valid UTF string.", __func__);
number = null_str;
}
@@ -325,7 +324,6 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
char null_str[] = "";
if (!sCallbackEnv.isValidUtf(at_string)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: at_string is not a valid UTF string.", __func__);
at_string = null_str;
}
@@ -361,7 +359,6 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
char null_str[] = "";
if (!sCallbackEnv.isValidUtf(at_string)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: at_string is not a valid UTF string.", __func__);
at_string = null_str;
}
diff --git a/android/app/jni/com_android_bluetooth_hfpclient.cpp b/android/app/jni/com_android_bluetooth_hfpclient.cpp
index c3e0c9842b5df59644f4c593d026285e628541fc..a36e19c75be3bb59e66aba8d0b75f95c638eac6b 100644
--- a/android/app/jni/com_android_bluetooth_hfpclient.cpp
+++ b/android/app/jni/com_android_bluetooth_hfpclient.cpp
@@ -170,7 +170,6 @@ static void current_operator_cb(const RawAddress* bd_addr, const char* name) {
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(name)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: name is not a valid UTF string.", __func__);
name = null_str;
}
@@ -246,7 +245,6 @@ static void clip_cb(const RawAddress* bd_addr, const char* number) {
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(number)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: number is not a valid UTF string.", __func__);
number = null_str;
}
@@ -267,7 +265,6 @@ static void call_waiting_cb(const RawAddress* bd_addr, const char* number) {
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(number)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: number is not a valid UTF string.", __func__);
number = null_str;
}
@@ -292,7 +289,6 @@ static void current_calls_cb(const RawAddress* bd_addr, int index,
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(number)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: number is not a valid UTF string.", __func__);
number = null_str;
}
@@ -338,7 +334,6 @@ static void subscriber_info_cb(const RawAddress* bd_addr, const char* name,
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(name)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: name is not a valid UTF string.", __func__);
name = null_str;
}
@@ -372,7 +367,6 @@ static void last_voice_tag_number_cb(const RawAddress* bd_addr,
const char null_str[] = "";
if (!sCallbackEnv.isValidUtf(number)) {
- android_errorWriteLog(0x534e4554, "109838537");
ALOGE("%s: number is not a valid UTF string.", __func__);
number = null_str;
}
diff --git a/android/app/jni/com_android_bluetooth_le_audio.cpp b/android/app/jni/com_android_bluetooth_le_audio.cpp
index c5cd51433af25dec450da00771350f0262b28bb4..58544df437712df11bbd5a76d3d59df068129402 100644
--- a/android/app/jni/com_android_bluetooth_le_audio.cpp
+++ b/android/app/jni/com_android_bluetooth_le_audio.cpp
@@ -532,6 +532,16 @@ static void setCcidInformationNative(JNIEnv* env, jobject object, jint ccid,
sLeAudioClientInterface->SetCcidInformation(ccid, contextType);
}
+static void setInCallNative(JNIEnv* env, jobject object, jboolean inCall) {
+ std::shared_lock lock(interface_mutex);
+ if (!sLeAudioClientInterface) {
+ LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface";
+ return;
+ }
+
+ sLeAudioClientInterface->SetInCall(inCall);
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "([Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V",
@@ -547,6 +557,7 @@ static JNINativeMethod sMethods[] = {
"BluetoothLeAudioCodecConfig;)V",
(void*)setCodecConfigPreferenceNative},
{"setCcidInformationNative", "(II)V", (void*)setCcidInformationNative},
+ {"setInCallNative", "(Z)V", (void*)setInCallNative},
};
/* Le Audio Broadcaster */
@@ -594,7 +605,7 @@ jbyteArray prepareRawLtvArray(
(const jbyte*)&kv_pair.first);
offset += 1;
// Value
- env->SetByteArrayRegion(raw_metadata, offset, 1,
+ env->SetByteArrayRegion(raw_metadata, offset, kv_pair.second.size(),
(const jbyte*)kv_pair.second.data());
offset += kv_pair.second.size();
}
@@ -828,7 +839,19 @@ jobject prepareBluetoothLeBroadcastMetadataObject(
return nullptr;
}
- ScopedLocalRef code(env, env->NewByteArray(sizeof(RawAddress)));
+ // Skip the leading null char bytes
+ int nativeCodeSize = 16;
+ int nativeCodeLeadingZeros = 0;
+ if (broadcast_metadata.broadcast_code) {
+ auto& nativeCode = broadcast_metadata.broadcast_code.value();
+ nativeCodeLeadingZeros =
+ std::find_if(nativeCode.cbegin(), nativeCode.cend(),
+ [](int x) { return x != 0x00; }) -
+ nativeCode.cbegin();
+ nativeCodeSize = nativeCode.size() - nativeCodeLeadingZeros;
+ }
+
+ ScopedLocalRef code(env, env->NewByteArray(nativeCodeSize));
if (!code.get()) {
LOG(ERROR) << "Failed to create new jbyteArray for the broadcast code";
return nullptr;
@@ -836,8 +859,10 @@ jobject prepareBluetoothLeBroadcastMetadataObject(
if (broadcast_metadata.broadcast_code) {
env->SetByteArrayRegion(
- code.get(), 0, sizeof(RawAddress),
- (jbyte*)broadcast_metadata.broadcast_code.value().data());
+ code.get(), 0, nativeCodeSize,
+ (const jbyte*)broadcast_metadata.broadcast_code->data() +
+ nativeCodeLeadingZeros);
+ CHECK(!env->ExceptionCheck());
}
return env->NewObject(
@@ -1130,10 +1155,18 @@ static void CreateBroadcastNative(JNIEnv* env, jobject object,
std::shared_lock lock(sBroadcasterInterfaceMutex);
if (!sLeAudioBroadcasterInterface) return;
- std::array code_array{};
+ std::array code_array{0};
if (broadcast_code) {
jsize size = env->GetArrayLength(broadcast_code);
- env->GetByteArrayRegion(broadcast_code, 0, size, (jbyte*)code_array.data());
+ if (size > 16) {
+ ALOGE("%s: broadcast code to long", __func__);
+ return;
+ }
+
+ // Padding with zeros on LSB positions if code is shorter than 16 octets
+ env->GetByteArrayRegion(
+ broadcast_code, 0, size,
+ (jbyte*)code_array.data() + code_array.size() - size);
}
jbyte* meta = env->GetByteArrayElements(metadata, nullptr);
diff --git a/android/app/res/values-af/strings.xml b/android/app/res/values-af/strings.xml
index 6e5004c4b5235b4991b982c9ebea1e03f8550ee4..91613cabd45dc6fdef9b596109f51f865daf7766 100644
--- a/android/app/res/values-af/strings.xml
+++ b/android/app/res/values-af/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-oudio"
"Lêers groter as 4 GB kan nie oorgedra word nie"
"Koppel aan Bluetooth"
+ "Bluetooth is aan in vliegtuigmodus"
+ "As jy Bluetooth aangeskakel hou, sal jou foon onthou om dit aan te hou wanneer jy weer in vliegtuigmodus is"
+ "Bluetooth bly aan"
+ "Jou foon onthou om Bluetooth aangeskakel te hou in vliegtuigmodus. Skakel Bluetooth af as jy nie wil hê dit moet aan bly nie."
+ "Wi-fi en Bluetooth bly aan"
+ "Jou foon onthou om wi‑fi en Bluetooth aan te hou in vliegtuigmodus. Skakel wi-fi en Bluetooth af as jy nie wil het hulle moet aan bly nie."
diff --git a/android/app/res/values-am/strings.xml b/android/app/res/values-am/strings.xml
index 9e534f28b4877a5cb1b256132cceeb7b2dbc6f6e..e75ee5d074c8bfc1e23f2987ed42d25850e58d51 100644
--- a/android/app/res/values-am/strings.xml
+++ b/android/app/res/values-am/strings.xml
@@ -104,7 +104,7 @@
"%1$s ተቀብሎ ተጠናቋል።"
"%1$s ልኮ ተጠናቋል።"
"ወደ ውስጥ ማስተላለፍ"
- "ወደ ውጪ ማስተላለፍ"
+ "ወደ ውጭ ማስተላለፍ"
"የዝውውር ታሪክ ባዶ ነው።"
"ሁሉም ዓይነቶች ከዝርዝር ውስጥ ይሰረዛሉ።"
"ብሉቱዝ ማጋሪያ፡ የተላኩ ፋይሎች"
@@ -128,4 +128,10 @@
"የብሉቱዝ ኦዲዮ"
"ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም"
"ከብሉቱዝ ጋር ተገናኝ"
+ "ብሉቱዝ በአውሮፕላን ሁነታ ላይ በርቷል"
+ "ብሉቱዝን አብርተው ካቆዩ በቀጣይ ጊዜ በአውሮፕላን ሁነታ ውስጥ ሲሆኑ ስልክዎ እሱን አብርቶ ማቆየቱን ያስታውሳል"
+ "ብሉቱዝ በርቶ ይቆያል"
+ "ስልክዎ ብሉቱዝን በአውሮፕላን ሁነታ ውስጥ አብርቶ ማቆየትን ያስታውሳል። በርቶ እንዲቆይ ካልፈለጉ ብሉቱዝን ያጥፉት።"
+ "Wi-Fi እና ብሉቱዝ በርተው ይቆያሉ"
+ "ስልክዎ Wi-Fiን እና ብሉቱዝን በአውሮፕላን ሁነታ ውስጥ አብርቶ ማቆየትን ያስታውሳል። በርተው እንዲቆዩ ካልፈለጉ Wi-Fi እና ብሉቱዝን ያጥፏቸው።"
diff --git a/android/app/res/values-ar/strings.xml b/android/app/res/values-ar/strings.xml
index 87b7ca6abcaa78a3dcab5dd69b243c6d96542203..f101cfa776259c30e5b353da61af11f259606d77 100644
--- a/android/app/res/values-ar/strings.xml
+++ b/android/app/res/values-ar/strings.xml
@@ -115,7 +115,7 @@
"فتح"
"محو من القائمة"
"محو"
- "التعرّف التلقائي على الموسيقى"
+ "قيد التشغيل الآن"
"حفظ"
"إلغاء"
"حدد الحسابات التي تريد مشاركتها عبر البلوتوث. لا يزال يتعين عليك قبول أي دخول إلى الحسابات أثناء الاتصال."
@@ -128,4 +128,10 @@
"بث صوتي عبر البلوتوث"
"يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت"
"الاتصال ببلوتوث"
+ "تقنية البلوتوث مفعّلة في \"وضع الطيران\""
+ "إذا واصلت تفعيل تقنية البلوتوث، سيتذكر هاتفك إبقاءها مفعَّلة في المرة القادمة التي تفعِّل فيها \"وضع الطيران\"."
+ "تظل تقنية البلوتوث مفعّلة"
+ "يتذكر هاتفك الاحتفاظ بتقنية البلوتوث مفعَّلة في \"وضع الطيران\". يمكنك إيقاف تقنية البلوتوث إذا لم تكن تريد مواصلة تفعيلها."
+ "تظل شبكة Wi‑Fi وتقنية البلوتوث مفعَّلتَين."
+ "يتذكر هاتفك الاحتفاظ بشبكة Wi‑Fi وتقنية البلوتوث مفعَّلتَين في \"وضع الطيران\". يمكنك إيقاف شبكة Wi‑Fi وتقنية البلوتوث إذا لم تكن تريد مواصلة تفعيلهما."
diff --git a/android/app/res/values-as/strings.xml b/android/app/res/values-as/strings.xml
index 4ecc5fbee3356824e27bb92e388005dbbc5d5360..526c3f00ec15ad345bef99b870b0c4a173f11e53 100644
--- a/android/app/res/values-as/strings.xml
+++ b/android/app/res/values-as/strings.xml
@@ -16,8 +16,8 @@
- "ডাউনল’ড মেনেজাৰ ব্যৱহাৰ কৰিব পাৰে।"
- "এপটোক BluetoothShare মেনেজাৰ ব্যৱহাৰ কৰি ফাইল স্থানান্তৰ কৰিবলৈ অনুমতি দিয়ে।"
+ "ডাউনল’ড মেনেজাৰ এক্সেছ কৰিব পাৰে।"
+ "এপ্টোক BluetoothShare মেনেজাৰ ব্যৱহাৰ কৰি ফাইল স্থানান্তৰ কৰিবলৈ অনুমতি দিয়ে।"
"ব্লুটুথ ডিভাইচ এক্সেছ কৰাৰ স্বীকৃতি দিয়ে।"
"এপ্টোক এটা ব্লুটুথ ডিভাইচ অস্থায়ীৰূপে স্বীকাৰ কৰাৰ অনুমতি দিয়ে যিয়ে ডিভাইচটোক ব্যৱহাৰকাৰীৰ নিশ্চিতিকৰণৰ অবিহনেই ইয়ালৈ ফাইল পঠিওৱাৰ অনুমতি দিয়ে।"
"ব্লুটুথ"
@@ -86,7 +86,7 @@
"ফাইলটো ছেভ কৰিব পৰাকৈ ইউএছবি ষ্ট’ৰেজত পৰ্যাপ্ত খালী ঠাই নাই।"
"ফাইলটো ছেভ কৰিব পৰাকৈ এছডি কাৰ্ডখনত পৰ্যাপ্ত খালী ঠাই নাই।"
"ইমান খালী ঠাইৰ দৰকাৰ: %1$s"
- "বহুত বেছি অনুৰোধৰ ওপৰত প্ৰক্ৰিয়া চলি আছে৷ পিছত আকৌ চেষ্টা কৰক৷"
+ "বহুত বেছি অনুৰোধৰ ওপৰত প্ৰক্ৰিয়া চলি আছে৷ পাছত আকৌ চেষ্টা কৰক৷"
"ফাইলৰ স্থানান্তৰণ এতিয়ালৈকে আৰম্ভ হোৱা নাই।"
"ফাইলৰ স্থানান্তৰণ চলি আছে।"
"ফাইলৰ স্থানান্তৰণৰ কাৰ্য সফলতাৰে সম্পন্ন কৰা হ’ল।"
@@ -118,7 +118,7 @@
"এতিয়া প্লে’ হৈ আছে"
"ছেভ কৰক"
"বাতিল কৰক"
- "ব্লুটুথৰ জৰিয়তে শ্বেয়াৰ কৰিব খোজা একাউণ্টসমূহ বাছক। তথাপিও সংযোগ কৰি থাকোঁতে আপুনি একাউণ্টসমূহক সকলো ধৰণৰ অনুমতি দিবই লাগিব।"
+ "ব্লুটুথৰ জৰিয়তে শ্বেয়াৰ কৰিব খোজা একাউণ্টসমূহ বাছক। তথাপিও সংযোগ কৰি থাকোঁতে আপুনি একাউণ্টসমূহক সকলো ধৰণৰ এক্সেছ দিবই লাগিব।"
"বাকী থকা শ্লটবোৰ:"
"এপ্লিকেশ্বন আইকন"
"ব্লুটুথৰ জৰিয়তে বাৰ্তা শ্বেয়াৰ কৰাৰ ছেটিং"
@@ -128,4 +128,10 @@
"ব্লুটুথ অডিঅ\'"
"৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি"
"ব্লুটুথৰ সৈতে সংযোগ কৰক"
+ "এয়াৰপ্লেন ম’ডত ব্লুটুথ অন হৈ থাকিব"
+ "আপুনি যদি ব্লুটুথ অন কৰি ৰাখে, পৰৱৰ্তী সময়ত আপুনি এয়াৰপ্লেন ম’ড ব্যৱহাৰ কৰিলে আপোনাৰ ফ’নটোৱে এয়া অন কৰি ৰাখিবলৈ মনত ৰাখিব"
+ "ব্লুটুথ অন হৈ থাকে"
+ "আপোনাৰ ফ’নটোৱে এয়াৰপ্লেন ম’ডত ব্লুটুথ অন ৰাখিবলৈ মনত ৰাখে। আপুনি যদি ব্লুটুথ অন হৈ থকাটো নিবিচাৰে, তেন্তে ইয়াক অফ কৰক।"
+ "ৱাই-ফাই আৰু ব্লুটুথ অন হৈ থাকে"
+ "আপোনাৰ ফ’নটোৱে এয়াৰপ্লেন ম’ডত ৱাই-ফাই আৰু ব্লুটুথ অন ৰাখিবলৈ মনত ৰাখে। আপুনি ৱাই-ফাই আৰু ব্লুটুথ অন হৈ থকাটো নিবিচাৰিলে সেইবোৰ অফ কৰক।"
diff --git a/android/app/res/values-as/strings_sap.xml b/android/app/res/values-as/strings_sap.xml
index f886e47fa17ce60bb1f36d6656f7700b89025392..f18138bb0b1ededdb6a24fe5e54495e8adb0051a 100644
--- a/android/app/res/values-as/strings_sap.xml
+++ b/android/app/res/values-as/strings_sap.xml
@@ -1,8 +1,8 @@
- "ব্লুটুথৰ ছিম ব্যৱহাৰ"
- "ব্লুটুথৰ ছিম ব্যৱহাৰ"
+ "ব্লুটুথৰ ছিম এক্সেছ"
+ "ব্লুটুথৰ ছিম এক্সেছ"
"সংযোগ বিচ্ছিন্ন কৰিবলৈ ক্লায়েণ্টক অনুৰোধ কৰিবনে?"
"সংযোগ বিচ্ছিন্ন কৰিবলৈ ক্লায়েণ্টৰ অপেক্ষা কৰি থকা হৈছে"
"বিচ্ছিন্ন কৰক"
diff --git a/android/app/res/values-az/strings.xml b/android/app/res/values-az/strings.xml
index 955699203d8dc8c571c8785bebf821472b9da0d6..0791d10fa68b18ba42ffce8012b186a677632f01 100644
--- a/android/app/res/values-az/strings.xml
+++ b/android/app/res/values-az/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"4GB-dən böyük olan faylları köçürmək mümkün deyil"
"Bluetooth\'a qoşulun"
+ "Bluetooth təyyarə rejimində aktivdir"
+ "Bluetooth\'u aktiv saxlasanız, növbəti dəfə təyyarə rejimində olduqda telefonunuz onu aktiv saxlayacaq"
+ "Bluetooth aktiv qalacaq"
+ "Telefonunuz təyyarə rejimində Bluetooth\'u aktiv saxlayacaq. Aktiv qalmasını istəmirsinizsə, Bluetooth\'u deaktiv edin."
+ "Wi-Fi və Bluetooth aktiv qalır"
+ "Telefonunuz təyyarə rejimində Wi‑Fi və Bluetooth\'u aktiv saxlayacaq. Aktiv qalmasını istəmirsinizsə, Wi-Fi və Bluetooth\'u deaktiv edin."
diff --git a/android/app/res/values-b+sr+Latn/strings.xml b/android/app/res/values-b+sr+Latn/strings.xml
index 92f842310f44023ab71eb2a706201f5aa353cc48..2590f7d4f2e536e11ce2e3ffc553a1bb3b26d15e 100644
--- a/android/app/res/values-b+sr+Latn/strings.xml
+++ b/android/app/res/values-b+sr+Latn/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"Ne mogu da se prenose datoteke veće od 4 GB"
"Poveži sa Bluetooth-om"
+ "Bluetooth je uključen u režimu rada u avionu"
+ "Ako odlučite da ne isključujete Bluetooth, telefon će zapamtiti da ga ne isključuje sledeći put kada budete u režimu rada u avionu"
+ "Bluetooth se ne isključuje"
+ "Telefon pamti da ne treba da isključuje Bluetooth u režimu rada u avionu. Isključite Bluetooth ako ne želite da ostane uključen."
+ "WiFi i Bluetooth ostaju uključeni"
+ "Telefon pamti da ne treba da isključuje WiFi i Bluetooth u režimu rada u avionu. Isključite WiFi i Bluetooth ako ne želite da ostanu uključeni."
diff --git a/android/app/res/values-be/strings.xml b/android/app/res/values-be/strings.xml
index ed72ab67c931e99023aaedfbc2b70b9ba5ca348f..434757afedcb3490ea43caf95bf1191d99a5dbfe 100644
--- a/android/app/res/values-be/strings.xml
+++ b/android/app/res/values-be/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-аўдыя"
"Немагчыма перадаць файлы, большыя за 4 ГБ"
"Падключыцца да Bluetooth"
+ "У рэжыме палёту Bluetooth уключаны"
+ "Калі вы не выключыце Bluetooth, падчас наступнага пераходу ў рэжым палёту тэлефон будзе захоўваць яго ўключаным"
+ "Bluetooth застаецца ўключаным"
+ "На тэлефоне ў рэжыме палёту Bluetooth застанецца ўключаным, але вы можаце выключыць яго."
+ "Wi-Fi і Bluetooth застаюцца ўключанымі"
+ "На тэлефоне ў рэжыме палёту сетка Wi‑Fi і Bluetooth будуць заставацца ўключанымі, але вы можаце выключыць іх."
diff --git a/android/app/res/values-bg/strings.xml b/android/app/res/values-bg/strings.xml
index b2d67422ab961d3435d82b706e8e961fe193bde7..882c273ce8d66062694468b8e70c99876d3abe7a 100644
--- a/android/app/res/values-bg/strings.xml
+++ b/android/app/res/values-bg/strings.xml
@@ -128,4 +128,10 @@
"Аудио през Bluetooth"
"Файловете с размер над 4 ГБ не могат да бъдат прехвърлени"
"Свързване с Bluetooth"
+ "Функцията за Bluetooth е включена в самолетния режим"
+ "Ако не изключите функцията за Bluetooth, телефонът ви ще я остави активна следващия път, когато използвате самолетния режим"
+ "Функцията за Bluetooth няма да се изключи"
+ "Функцията за Bluetooth ще бъде включена, докато телефонът ви е в самолетен режим. Ако не искате това, изключете я."
+ "Функциите за Wi-Fi и Bluetooth няма да бъдат изключени"
+ "Функциите за Wi‑Fi и Bluetooth ще бъдат включени, докато телефонът ви е в самолетен режим. Ако не искате това, изключете ги."
diff --git a/android/app/res/values-bn/strings.xml b/android/app/res/values-bn/strings.xml
index c85dbf3774306aed42d16b71fc2fc29bf71dde7d..7e35d1e02d929723c8f8f5252397f92ff847c039 100644
--- a/android/app/res/values-bn/strings.xml
+++ b/android/app/res/values-bn/strings.xml
@@ -128,4 +128,10 @@
"ব্লুটুথ অডিও"
"৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না"
"ব্লুটুথের সাথে কানেক্ট করুন"
+ "\'বিমান মোড\'-এ থাকাকালীন ব্লুটুথ চালু থাকে"
+ "আপনি ওয়াই-ফাই চালু রাখলে, আপনি এরপর \'বিমান মোডে\' থাকলে আপনার ফোন এটি চালু রাখবে"
+ "ব্লুটুথ চালু থাকে"
+ "\'বিমান মোড\'-এ থাকাকালীন আপনার ফোন ব্লুটুথ চালু রাখে। আপনি ব্লুটুথ চালু না রাখতে চাইলে এটি বন্ধ করুন।"
+ "ওয়াই-ফাই ও ব্লুটুথ চালু থাকে"
+ "\'বিমান মোড\'-এ থাকাকালীন আপনার ফোন ওয়াই-ফাই ও ব্লুটুথ চালু রাখে। আপনি যদি ওয়াই-ফাই এবং ব্লুটুথ চালু রাখতে না চান, সেগুলি বন্ধ করে দিন।"
diff --git a/android/app/res/values-bs/strings.xml b/android/app/res/values-bs/strings.xml
index c4fadb0db316e32d230e6b49c4f8bb8979065a97..8c57a628a6b8951d1680c2f2224dcd64bd7a11a6 100644
--- a/android/app/res/values-bs/strings.xml
+++ b/android/app/res/values-bs/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Nije moguće prenijeti fajlove veće od 4 GB"
"Poveži se na Bluetooth"
+ "Bluetooth je uključen u načinu rada u avionu"
+ "Ako ostavite Bluetooth uključenim, telefon će zapamtiti da ga ostavi uključenog sljedeći put kada budete u načinu rada u avionu"
+ "Bluetooth ostaje uključen"
+ "Telefon pamti da Bluetooth treba biti uključen u načinu rada u avionu. Isključite Bluetooth ako ne želite da ostane uključen."
+ "WiFi i Bluetooth ostaju uključeni"
+ "Telefon pamti da WiFi i Bluetooth trebaju biti uključeni u načinu rada u avionu. Isključite WiFi i Bluetooth ako ne želite da ostanu uključeni."
diff --git a/android/app/res/values-ca/strings.xml b/android/app/res/values-ca/strings.xml
index b5ec84230866829593c5eee6c3919043362b351f..46e1afa41c285824e23035556928a9b91fb7ce8d 100644
--- a/android/app/res/values-ca/strings.xml
+++ b/android/app/res/values-ca/strings.xml
@@ -128,4 +128,10 @@
"Àudio per Bluetooth"
"No es poden transferir fitxers més grans de 4 GB"
"Connecta el Bluetooth"
+ "El Bluetooth està activat en mode d\'avió"
+ "Si tens activat el Bluetooth, el telèfon recordarà mantenir-lo així la pròxima vegada que utilitzis el mode d\'avió"
+ "El Bluetooth es mantindrà activat"
+ "El telèfon recorda mantenir el Bluetooth activat en mode d\'avió. Desactiva el Bluetooth si no vols que es quedi activat."
+ "La Wi‑Fi i el Bluetooth es mantenen activats"
+ "El telèfon recorda mantenir la Wi‑Fi i el Bluetooth activats en mode d\'avió. Desactiva la Wi‑Fi i el Bluetooth si no vols que es quedin activats."
diff --git a/android/app/res/values-cs/strings.xml b/android/app/res/values-cs/strings.xml
index 5352a25bece2a61d6c2dcdd833fe1e13f2103afb..33f3bfc6a256492aee3eb34313561825932304b8 100644
--- a/android/app/res/values-cs/strings.xml
+++ b/android/app/res/values-cs/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Soubory větší než 4 GB nelze přenést"
"Připojit k Bluetooth"
+ "Zapnutý Bluetooth v režimu Letadlo"
+ "Pokud Bluetooth necháte zapnutý, telefon si zapamatuje, že ho má příště v režimu Letadlo ponechat zapnutý"
+ "Bluetooth zůstane zapnutý"
+ "Telefon si pamatuje, že má v režimu Letadlo ponechat zapnutý Bluetooth. Pokud nechcete, aby Bluetooth zůstal zapnutý, vypněte ho."
+ "Wi-Fi a Bluetooth zůstávají zapnuté"
+ "Telefon si pamatuje, že má v režimu Letadlo ponechat zapnutou Wi-Fi a Bluetooth. Pokud nechcete, aby Wi-Fi a Bluetooth zůstaly zapnuté, vypněte je."
diff --git a/android/app/res/values-da/strings.xml b/android/app/res/values-da/strings.xml
index 15881ed4f186d203fd07af289544201faa6cf6be..b3ac49d8cef2beacdfacbd6159cd7a5254fb0735 100644
--- a/android/app/res/values-da/strings.xml
+++ b/android/app/res/values-da/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-lyd"
"File, der er større end 4 GB, kan ikke overføres"
"Opret forbindelse til Bluetooth"
+ "Bluetooth er aktiveret i flytilstand"
+ "Hvis du holder Bluetooth aktiveret, sørger din telefon for, at Bluetooth forbliver aktiveret, næste gang du sætter den til flytilstand"
+ "Bluetooth forbliver aktiveret"
+ "Din telefon beholder Bluetooth aktiveret i flytilstand. Deaktiver Bluetooth, hvis du ikke vil have, at det forbliver aktiveret."
+ "Wi-Fi og Bluetooth forbliver aktiveret"
+ "Din telefon husker at holde Wi-Fi og Bluetooth aktiveret i flytilstand. Deaktiver Wi-Fi og Bluetooth, hvis du ikke vil have, at de forbliver aktiveret."
diff --git a/android/app/res/values-de/strings.xml b/android/app/res/values-de/strings.xml
index 719dd9532b00fdd60ec1d3ed8336b80371fdd771..82283c36de83657bd8103b74af6f8bee9a41caf4 100644
--- a/android/app/res/values-de/strings.xml
+++ b/android/app/res/values-de/strings.xml
@@ -80,9 +80,9 @@
"Die Datei wird empfangen. Überprüfe den Fortschritt in der Benachrichtigungskonsole."
"Die Datei kann nicht empfangen werden."
"Der Empfang der Datei von \"%1$s\" wurde angehalten."
- "Datei wird an \"%1$s\" gesendet..."
+ "Datei wird an „%1$s“ gesendet..."
"%1$s Dateien werden an \"%2$s\" gesendet."
- "Die Übertragung der Datei an \"%1$s\" wurde abgebrochen"
+ "Die Übertragung der Datei an „%1$s“ wurde abgebrochen"
"Auf dem USB-Speicher ist nicht genügend Platz, um die Datei zu speichern."
"Auf der SD-Karte ist nicht genügend Platz, um die Datei zu speichern."
"Erforderlicher Speicherplatz: %1$s"
@@ -128,4 +128,10 @@
"Bluetooth-Audio"
"Dateien mit mehr als 4 GB können nicht übertragen werden"
"Mit Bluetooth verbinden"
+ "Bluetooth im Flugmodus eingeschaltet"
+ "Wenn du Bluetooth nicht ausschaltest, bleibt es eingeschaltet, wenn du das nächste Mal in den Flugmodus wechselst"
+ "Bluetooth bleibt aktiviert"
+ "Auf deinem Smartphone bleibt Bluetooth im Flugmodus eingeschaltet. Schalte Bluetooth aus, wenn du das nicht möchtest."
+ "WLAN und Bluetooth bleiben eingeschaltet"
+ "Auf deinem Smartphone bleiben WLAN und Bluetooth im Flugmodus eingeschaltet. Schalte sie aus, wenn du das nicht möchtest."
diff --git a/android/app/res/values-el/strings.xml b/android/app/res/values-el/strings.xml
index 1b6ef5852e5dbaaefde8ff5c3d4e6106ce8fafe8..6afca1f394ed0c2fceb053119b95b394e7e59661 100644
--- a/android/app/res/values-el/strings.xml
+++ b/android/app/res/values-el/strings.xml
@@ -128,4 +128,10 @@
"Ήχος Bluetooth"
"Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB"
"Σύνδεση σε Bluetooth"
+ "Bluetooth ενεργοποιημένο σε λειτουργία πτήσης"
+ "Αν διατηρήσετε το Bluetooth ενεργοποιημένο, το τηλέφωνό σας θα θυμάται να το διατηρήσει ενεργοποιημένο την επόμενη φορά που θα βρεθεί σε λειτουργία πτήσης"
+ "Το Bluetooth παραμένει ενεργό"
+ "Το τηλέφωνο θυμάται να διατηρεί ενεργοποιημένο το Bluetooth σε λειτουργία πτήσης. Απενεργοποιήστε το Bluetooth αν δεν θέλετε να παραμένει ενεργοποιημένο."
+ "Το Wi-Fi και το Bluetooth παραμένουν ενεργοποιημένα"
+ "Το τηλέφωνο θυμάται να διατηρεί ενεργοποιημένο το Wi‑Fi και το Bluetooth σε λειτουργία πτήσης. Απενεργοποιήστε το Wi-Fi και το Bluetooth αν δεν θέλετε να παραμένουν ενεργοποιημένα."
diff --git a/android/app/res/values-en-rAU/strings.xml b/android/app/res/values-en-rAU/strings.xml
index 266667fd536b7ea4cc128923a467605815660654..724d1e6959912a0e7b7b6e05849589b8a65a581a 100644
--- a/android/app/res/values-en-rAU/strings.xml
+++ b/android/app/res/values-en-rAU/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"Files bigger than 4 GB cannot be transferred"
"Connect to Bluetooth"
+ "Bluetooth on in aeroplane mode"
+ "If you keep Bluetooth on, your phone will remember to keep it on the next time that you\'re in aeroplane mode"
+ "Bluetooth stays on"
+ "Your phone remembers to keep Bluetooth on in aeroplane mode. Turn off Bluetooth if you don\'t want it to stay on."
+ "Wi-Fi and Bluetooth stay on"
+ "Your phone remembers to keep Wi-Fi and Bluetooth on in aeroplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on."
diff --git a/android/app/res/values-en-rCA/strings.xml b/android/app/res/values-en-rCA/strings.xml
index 89e7e55847b2cb3ba7044cc690a1cab11706c6ab..c812ceed90e1f9a3e74c7da0c49227fb81de3bae 100644
--- a/android/app/res/values-en-rCA/strings.xml
+++ b/android/app/res/values-en-rCA/strings.xml
@@ -17,8 +17,8 @@
"Access download manager."
- "Allows the application to access the Bluetooth Share manager and to use it to transfer files."
- "Acceptlist Bluetooth device access."
+ "Allows the app to access the BluetoothShare manager and use it to transfer files."
+ "Acceptlist bluetooth device access."
"Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation."
"Bluetooth"
"Unknown device"
@@ -87,13 +87,13 @@
"There isn\'t enough space on the SD card to save the file."
"Space needed: %1$s"
"Too many requests are being processed. Try again later."
- "File transfer not started yet"
+ "File transfer not started yet."
"File transfer is ongoing."
"File transfer completed successfully."
"Content isn\'t supported."
"Transfer forbidden by target device."
- "Transfer cancelled by user."
- "Storage issue"
+ "Transfer canceled by user."
+ "Storage issue."
"No USB storage."
"No SD card. Insert an SD card to save transferred files."
"Connection unsuccessful."
@@ -115,17 +115,23 @@
"Open"
"Clear from list"
"Clear"
- "Now playing"
+ "Now Playing"
"Save"
"Cancel"
- "Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."
+ "Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."
"Slots left:"
- "Application icon"
- "Bluetooth message sharing settings"
+ "Application Icon"
+ "Bluetooth Message Sharing Settings"
"Cannot select account. 0 slots left"
"Bluetooth audio connected"
"Bluetooth audio disconnected"
- "Bluetooth audio"
- "Files bigger than 4 GB cannot be transferred"
+ "Bluetooth Audio"
+ "Files bigger than 4GB cannot be transferred"
"Connect to Bluetooth"
+ "Bluetooth on in Airplane mode"
+ "If you keep Bluetooth on, your phone will remember to keep it on the next time you\'re in Airplane mode"
+ "Bluetooth stays on"
+ "Your phone remembers to keep Bluetooth on in Airplane mode. Turn off Bluetooth if you don\'t want it to stay on."
+ "Wi-Fi and Bluetooth stay on"
+ "Your phone remembers to keep Wi-Fi and Bluetooth on in Airplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on."
diff --git a/android/app/res/values-en-rCA/strings_pbap.xml b/android/app/res/values-en-rCA/strings_pbap.xml
index c7b8dc853ba7038f1f0ee413a45f1804befa203b..eb205ce24874d1a54c4efc8c4268f236972d4ae6 100644
--- a/android/app/res/values-en-rCA/strings_pbap.xml
+++ b/android/app/res/values-en-rCA/strings_pbap.xml
@@ -4,11 +4,11 @@
"Type session key for %1$s"
"Bluetooth session key required"
"There was time out to accept connection with %1$s"
- "There was a timeout to input session key with %1$s"
+ "There was time out to input session key with %1$s"
"Obex authentication request"
- "Session key"
+ "Session Key"
"Type session key for %1$s"
- "Car Kit"
+ "Carkit"
"Unknown name"
"My name"
"000000"
diff --git a/android/app/res/values-en-rCA/strings_sap.xml b/android/app/res/values-en-rCA/strings_sap.xml
index 29288d1eff1a0df87e15c5d701a24e173baa08a7..0656641288869273e67927b0dce66a75e62ceef9 100644
--- a/android/app/res/values-en-rCA/strings_sap.xml
+++ b/android/app/res/values-en-rCA/strings_sap.xml
@@ -2,7 +2,7 @@
"Bluetooth SIM access"
- "Bluetooth SIM access"
+ "Bluetooth SIM Access"
"Request client to disconnect?"
"Waiting for client to disconnect"
"Disconnect"
diff --git a/android/app/res/values-en-rGB/strings.xml b/android/app/res/values-en-rGB/strings.xml
index 266667fd536b7ea4cc128923a467605815660654..724d1e6959912a0e7b7b6e05849589b8a65a581a 100644
--- a/android/app/res/values-en-rGB/strings.xml
+++ b/android/app/res/values-en-rGB/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"Files bigger than 4 GB cannot be transferred"
"Connect to Bluetooth"
+ "Bluetooth on in aeroplane mode"
+ "If you keep Bluetooth on, your phone will remember to keep it on the next time that you\'re in aeroplane mode"
+ "Bluetooth stays on"
+ "Your phone remembers to keep Bluetooth on in aeroplane mode. Turn off Bluetooth if you don\'t want it to stay on."
+ "Wi-Fi and Bluetooth stay on"
+ "Your phone remembers to keep Wi-Fi and Bluetooth on in aeroplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on."
diff --git a/android/app/res/values-en-rIN/strings.xml b/android/app/res/values-en-rIN/strings.xml
index 266667fd536b7ea4cc128923a467605815660654..724d1e6959912a0e7b7b6e05849589b8a65a581a 100644
--- a/android/app/res/values-en-rIN/strings.xml
+++ b/android/app/res/values-en-rIN/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"Files bigger than 4 GB cannot be transferred"
"Connect to Bluetooth"
+ "Bluetooth on in aeroplane mode"
+ "If you keep Bluetooth on, your phone will remember to keep it on the next time that you\'re in aeroplane mode"
+ "Bluetooth stays on"
+ "Your phone remembers to keep Bluetooth on in aeroplane mode. Turn off Bluetooth if you don\'t want it to stay on."
+ "Wi-Fi and Bluetooth stay on"
+ "Your phone remembers to keep Wi-Fi and Bluetooth on in aeroplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on."
diff --git a/android/app/res/values-en-rXC/strings.xml b/android/app/res/values-en-rXC/strings.xml
index a47fdcdbc389587ad75da2055764316ebba7c983..d925306cbadd7a632a677ae21e6a294a2f079340 100644
--- a/android/app/res/values-en-rXC/strings.xml
+++ b/android/app/res/values-en-rXC/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Files bigger than 4GB cannot be transferred"
"Connect to Bluetooth"
+ "Bluetooth on in airplane mode"
+ "If you keep Bluetooth on, your phone will remember to keep it on the next time you\'re in airplane mode"
+ "Bluetooth stays on"
+ "Your phone remembers to keep Bluetooth on in airplane mode. Turn off Bluetooth if you don\'t want it to stay on."
+ "Wi-Fi and Bluetooth stay on"
+ "Your phone remembers to keep Wi-Fi and Bluetooth on in airplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on."
diff --git a/android/app/res/values-es-rUS/strings.xml b/android/app/res/values-es-rUS/strings.xml
index d71996e76a667618bc322d32e4b523b43d82eaaf..89d6b57d7c306f44aa59e3aca10b360d5425302e 100644
--- a/android/app/res/values-es-rUS/strings.xml
+++ b/android/app/res/values-es-rUS/strings.xml
@@ -128,4 +128,10 @@
"Audio Bluetooth"
"No se pueden transferir los archivos de más de 4 GB"
"Conectarse a Bluetooth"
+ "Bluetooth activado en modo de avión"
+ "Si mantienes el Bluetooth activado, el teléfono lo dejará activado la próxima vez que actives el modo de avión"
+ "El Bluetooth permanece activado"
+ "El teléfono dejará activado el Bluetooth en el modo de avión. Desactívalo si no quieres que permanezca activado."
+ "El Wi-Fi y el Bluetooth permanecen activados"
+ "El teléfono dejará activado el Wi-Fi y el Bluetooth en el modo de avión. Si no quieres que permanezcan activados, desactívalos."
diff --git a/android/app/res/values-es/strings.xml b/android/app/res/values-es/strings.xml
index dcfe458023c9f153a165de1ebb6297af1ff6a14a..be8fb70750689b08a0e1c264c9ed06c19cf46c2a 100644
--- a/android/app/res/values-es/strings.xml
+++ b/android/app/res/values-es/strings.xml
@@ -128,4 +128,10 @@
"Audio por Bluetooth"
"No se pueden transferir archivos de más de 4 GB"
"Conectarse a un dispositivo Bluetooth"
+ "Bluetooth activado en modo Avión"
+ "Si dejas el Bluetooth activado, tu teléfono se acordará de mantenerlo así la próxima vez que uses el modo Avión"
+ "El Bluetooth permanece activado"
+ "Tu teléfono se acordará de mantener activado el Bluetooth en modo Avión. Desactiva el Bluetooth si no quieres que permanezca activado."
+ "El Wi-Fi y el Bluetooth permanecen activados"
+ "Tu teléfono se acordará de mantener activados el Wi-Fi y el Bluetooth en modo Avión. Desactívalos si no quieres que permanezcan activados."
diff --git a/android/app/res/values-et/strings.xml b/android/app/res/values-et/strings.xml
index f4c8c87d62897ed09758a219c91c0a754cbc1711..12b4e558da7f9e858a24f7e65e04cdadfde8f2c1 100644
--- a/android/app/res/values-et/strings.xml
+++ b/android/app/res/values-et/strings.xml
@@ -128,4 +128,10 @@
"Bluetoothi heli"
"Faile, mis on üle 4 GB, ei saa üle kanda"
"Ühenda Bluetoothiga"
+ "Bluetooth on lennukirežiimis sisse lülitatud"
+ "Kui hoiate Bluetoothi sisselülitatuna, jätab telefon teie valiku meelde ja kasutab seda järgmisel korral lennukirežiimi aktiveerimisel."
+ "Bluetooth jääb sisselülitatuks"
+ "Teie telefon hoiab Bluetoothi lennukirežiimis sisselülitatuna. Lülitage Bluetooth välja, kui te ei soovi, et see oleks sisse lülitatud."
+ "WiFi ja Bluetoothi jäävad sisselülitatuks"
+ "Teie telefon hoiab WiFi ja Bluetoothi lennukirežiimis sisselülitatuna. Lülitage WiFi ja Bluetooth välja, kui te ei soovi, et need oleksid sisse lülitatud."
diff --git a/android/app/res/values-eu/strings.xml b/android/app/res/values-eu/strings.xml
index d48e5a06f3547b11c492934222406a14bcb65db7..872bbacdde78ea9f4f4575e08d41abc958f62f0d 100644
--- a/android/app/res/values-eu/strings.xml
+++ b/android/app/res/values-eu/strings.xml
@@ -20,14 +20,14 @@
"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzeko baimena ematen die aplikazioei."
"Ezarri Bluetooth bidezko gailuak onartutakoen zerrendan."
"Bluetooth bidezko gailu bat aldi baterako onartutakoen zerrendan ezartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."
- "Bluetooth-a"
+ "Bluetootha"
"Identifikatu ezin den gailua"
"Ezezaguna"
"Hegaldi modua"
- "Ezin duzu erabili Bluetooth-a Hegaldi moduan."
+ "Ezin duzu erabili Bluetootha Hegaldi moduan."
- "Bluetooth-zerbitzuak erabiltzeko, Bluetooth-a aktibatu behar duzu."
- "Bluetooth-a aktibatu nahi duzu?"
+ "Bluetooth-zerbitzuak erabiltzeko, Bluetootha aktibatu behar duzu."
+ "Bluetootha aktibatu nahi duzu?"
"Utzi"
"Aktibatu"
"Fitxategi-transferentzia"
@@ -76,7 +76,7 @@
"Ez dago fitxategirik"
"Ez dago horrelako fitxategirik. \n"
"Itxaron…"
- "Bluetooth-a aktibatzen…"
+ "Bluetootha aktibatzen…"
"Fitxategia jasoko da. Egoera kontrolatzeko, joan Jakinarazpenen panelera."
"Ezin da fitxategia jaso."
"\"%1$s\" igorlearen fitxategia jasotzeari utzi zaio"
@@ -127,5 +127,11 @@
"Deskonektatu da Bluetooth bidezko audioa"
"Bluetooth bidezko audioa"
"Ezin dira transferitu 4 GB baino gehiagoko fitxategiak"
- "Konektatu Bluetooth-era"
+ "Konektatu Bluetoothera"
+ "Bluetootha aktibatuta mantentzen da hegaldi moduan"
+ "Bluetootha aktibatuta utziz gero, hura aktibatuta mantentzeaz gogoratuko da telefonoa hegaldi modua erabiltzen duzun hurrengoan"
+ "Bluetootha aktibatuta mantenduko da"
+ "Hegaldi moduan, Bluetootha aktibatuta mantentzeaz gogoratzen da telefonoa. Halakorik nahi ez baduzu, desaktiba ezazu zuk zeuk."
+ "Wifia eta Bluetootha aktibatuta mantentzen dira"
+ "Hegaldi moduan, wifia eta Bluetootha aktibatuta mantentzeaz gogoratzen da telefonoa. Halakorik nahi ez baduzu, desaktiba itzazu zuk zeuk."
diff --git a/android/app/res/values-eu/test_strings.xml b/android/app/res/values-eu/test_strings.xml
index e6016491941356221b070e29ccc92f7b4df2b76f..4c501ab19e1bb6c4ce2e378cbca368589ab0d067 100644
--- a/android/app/res/values-eu/test_strings.xml
+++ b/android/app/res/values-eu/test_strings.xml
@@ -1,7 +1,7 @@
- "Bluetooth-a"
+ "Bluetootha"
"Sartu erregistroa"
"Berretsi erregistroa"
"ACK erregistroa"
diff --git a/android/app/res/values-fa/strings.xml b/android/app/res/values-fa/strings.xml
index 7fe21bde4ff089ac9ec1288fbc3f90d62065bd08..d2507b1647ae9892ce9e43f64714f39181a9d2ba 100644
--- a/android/app/res/values-fa/strings.xml
+++ b/android/app/res/values-fa/strings.xml
@@ -128,4 +128,10 @@
"بلوتوث صوتی"
"فایلهای بزرگتر از ۴ گیگابایت نمیتوانند منتقل شوند"
"اتصال به بلوتوث"
+ "بلوتوث در «حالت هواپیما» روشن باشد"
+ "اگر بلوتوث را روشن نگه دارید، تلفنتان بهیاد خواهد داشت تا دفعه بعدی که در «حالت هواپیما» هستید آن را روشن نگه دارد"
+ "بلوتوث روشن بماند"
+ "تلفنتان بهیاد میآورد که بلوتوث را در «حالت هواپیما» روشن نگه دارد. اگر نمیخواهید بلوتوث روشن بماند، آن را خاموش کنید."
+ "Wi-Fi و بلوتوث روشن بماند"
+ "تلفنتان بهیاد میآورد که Wi-Fi و بلوتوث را در «حالت هواپیما» روشن نگه دارد. اگر نمیخواهید Wi-Fi و بلوتوث روشن بمانند، آنها را خاموش کنید."
diff --git a/android/app/res/values-fi/strings.xml b/android/app/res/values-fi/strings.xml
index 75c005f3a4a336e5cf8b272df3a01a3c3b1bd8da..bc3550fc50e9688c4a4ecbb0d8cf93c037d4cd24 100644
--- a/android/app/res/values-fi/strings.xml
+++ b/android/app/res/values-fi/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-ääni"
"Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää."
"Muodosta Bluetooth-yhteys"
+ "Bluetooth päällä lentokonetilassa"
+ "Jos pidät Bluetooth-yhteyden päällä, puhelin pitää sen päällä, kun seuraavan kerran olet lentokonetilassa"
+ "Bluetooth pysyy päällä"
+ "Puhelimen Bluetooth pysyy päällä lentokonetilassa. Voit halutessasi laittaa Bluetooth-yhteyden pois päältä."
+ "Wi-Fi ja Bluetooth pysyvät päällä"
+ "Puhelimen Wi-Fi-yhteys ja Bluetooth pysyvät päällä lentokonetilassa. Voit halutessasi laittaa ne pois päältä."
diff --git a/android/app/res/values-fr-rCA/strings.xml b/android/app/res/values-fr-rCA/strings.xml
index 5e10989fa304b01691997aa05ae9d3136992ab60..1977da0f9eae1ee415352e7adcae3ed7ce4de19a 100644
--- a/android/app/res/values-fr-rCA/strings.xml
+++ b/android/app/res/values-fr-rCA/strings.xml
@@ -80,9 +80,9 @@
"La réception du fichier va commencer. La progression va s\'afficher dans le panneau de notification."
"Impossible de recevoir le fichier."
"Réception du fichier de \"%1$s\" interrompue"
- "Envoi du fichier à \"%1$s\""
+ "Envoi du fichier à « %1$s »"
"Envoi de %1$s fichiers à \"%2$s\""
- "Envoi du fichier à \"%1$s\" interrompu"
+ "Envoi du fichier à « %1$s » interrompu"
"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier."
"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier."
"Espace requis : %1$s"
@@ -128,4 +128,10 @@
"Audio Bluetooth"
"Les fichiers dépassant 4 Go ne peuvent pas être transférés"
"Connexion au Bluetooth"
+ "Bluetooth activé en mode Avion"
+ "Si vous laissez le Bluetooth activé, votre téléphone se souviendra qu\'il doit le laisser activé la prochaine fois que vous serez en mode Avion"
+ "Le Bluetooth reste activé"
+ "Votre téléphone se souvient de garder le Bluetooth activé en mode Avion. Désactivez le Bluetooth si vous ne souhaitez pas qu\'il reste activé."
+ "Le Wi-Fi et le Bluetooth restent activés"
+ "Votre téléphone se souvient de garder le Wi-Fi et le Bluetooth activés en mode Avion. Désactivez le Wi-Fi et le Bluetooth si vous ne souhaitez pas qu\'ils restent activés."
diff --git a/android/app/res/values-fr/strings.xml b/android/app/res/values-fr/strings.xml
index 333b95ed8a419d0e417616a667cc52007fe24977..48a84dc0025edc6f36c54ec15353db04f1aa4ef4 100644
--- a/android/app/res/values-fr/strings.xml
+++ b/android/app/res/values-fr/strings.xml
@@ -128,4 +128,10 @@
"Audio Bluetooth"
"Impossible de transférer les fichiers supérieurs à 4 Go"
"Se connecter au Bluetooth"
+ "Bluetooth activé en mode Avion"
+ "Si vous laissez le Bluetooth activé, votre téléphone s\'en souviendra et le Bluetooth restera activé la prochaine fois que vous serez en mode Avion"
+ "Le Bluetooth reste activé"
+ "Le Bluetooth restera activé en mode Avion. Vous pouvez le désactiver si vous le souhaitez."
+ "Le Wi-Fi et le Bluetooth restent activés"
+ "Le Wi‑Fi et le Bluetooth de votre téléphone resteront activés en mode Avion. Vous pouvez les désactivez si vous le souhaitez."
diff --git a/android/app/res/values-gl/strings.xml b/android/app/res/values-gl/strings.xml
index a9b54c3fd8e574abf689ce10f93367dd6f0e2eca..d46049d8e9d1ba8cb8a3c208225dc16d244cefa7 100644
--- a/android/app/res/values-gl/strings.xml
+++ b/android/app/res/values-gl/strings.xml
@@ -128,4 +128,10 @@
"Audio por Bluetooth"
"Non se poden transferir ficheiros de máis de 4 GB"
"Conectar ao Bluetooth"
+ "Bluetooth activado no modo avión"
+ "Se mantés o Bluetooth activado, o teléfono lembrará que ten que deixalo nese estado a próxima vez que esteas no modo avión"
+ "O Bluetooth permanece activado"
+ "O teu teléfono lembrará manter o Bluetooth activado no modo avión. Se non queres que permaneza nese estado, desactívao."
+ "A wifi e o Bluetooth permanecen activados"
+ "O teu teléfono lembrará manter a wifi e o Bluetooth activados no modo avión. Se non queres que permanezan nese estado, desactívaos."
diff --git a/android/app/res/values-gu/strings.xml b/android/app/res/values-gu/strings.xml
index 3653a8aaace9722cfda3f0b3d0fafc606cc63a16..1832689dd363b74fc0fc467cbb5f82637dd4d53b 100644
--- a/android/app/res/values-gu/strings.xml
+++ b/android/app/res/values-gu/strings.xml
@@ -128,4 +128,10 @@
"બ્લૂટૂથ ઑડિઓ"
"4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી"
"બ્લૂટૂથ સાથે કનેક્ટ કરો"
+ "એરપ્લેન મોડમાં બ્લૂટૂથ ચાલુ છે"
+ "જો તમે બ્લૂટૂથ ચાલુ રાખો, તો તમે જ્યારે આગલી વખતે એરપ્લેન મોડ પર જશો, ત્યારે તમારો ફોન તેને ચાલુ રાખવાનું યાદ રાખશે"
+ "બ્લૂટૂથ ચાલુ રહેશે"
+ "તમારો ફોન બ્લૂટૂથને એરપ્લેન મોડમાં ચાલુ રાખવાનું યાદ રાખે છે. જો તમે બ્લૂટૂથ ચાલુ રાખવા માગતા ન હો, તો તેને બંધ કરો."
+ "વાઇ-ફાઇ અને બ્લૂટૂથ ચાલુ રહે છે"
+ "તમારો ફોન વાઇ-ફાઇ અને બ્લૂટૂથને એરપ્લેન મોડમાં ચાલુ રાખવાનું યાદ રાખે છે. જો તમે વાઇ-ફાઇ અને બ્લૂટૂથ ચાલુ રાખવા માગતા ન હો, તો તેને બંધ કરો."
diff --git a/android/app/res/values-hi/strings.xml b/android/app/res/values-hi/strings.xml
index 99dab10299894682815eb5a29d599b939ea828d1..df6c33daf311e60bd6c01003f943a8a5eafe54af 100644
--- a/android/app/res/values-hi/strings.xml
+++ b/android/app/res/values-hi/strings.xml
@@ -128,4 +128,10 @@
"ब्लूटूथ ऑडियो"
"4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं"
"ब्लूटूथ से कनेक्ट करें"
+ "हवाई जहाज़ मोड में ब्लूटूथ चालू है"
+ "ब्लूटूथ चालू रखने पर आपका फ़ोन, अगली बार हवाई जहाज़ मोड चालू होने पर भी ब्लूटूथ चालू रखेगा"
+ "ब्लूटूथ चालू रहता है"
+ "हवाई जहाज़ मोड में भी, आपका फ़ोन ब्लूटूथ चालू रखता है. अगर ब्लूटूथ चालू नहीं रखना है, तो उसे बंद कर दें."
+ "वाई-फ़ाई और ब्लूटूथ चालू रहते हैं"
+ "हवाई जहाज़ मोड में भी, आपका फ़ोन वाई-फ़ाई और ब्लूटूथ को चालू रखता है. अगर आपको वाई-फ़ाई और ब्लूटूथ चालू नहीं रखना है, तो उन्हें बंद कर दें."
diff --git a/android/app/res/values-hr/strings.xml b/android/app/res/values-hr/strings.xml
index fc136d79b0902a0b7ce2b43b847d0c030d943354..69df52142e0206f55090e2888b3c05294ebd65eb 100644
--- a/android/app/res/values-hr/strings.xml
+++ b/android/app/res/values-hr/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Datoteke veće od 4 GB ne mogu se prenijeti"
"Povezivanje s Bluetoothom"
+ "Bluetooth je uključen u načinu rada u zrakoplovu"
+ "Ako Bluetooth ostane uključen, telefon će zapamtiti da treba ostati uključen u načinu rada u zrakoplovu"
+ "Bluetooth ostaje uključen"
+ "Telefon će zapamtiti da Bluetooth treba ostati uključen u načinu rada u zrakoplovu. Isključite Bluetooth ako ne želite da ostane uključen."
+ "Wi-Fi i Bluetooth ostat će uključeni"
+ "Telefon će zapamtiti da Wi‑Fi i Bluetooth trebaju ostati uključeni u načinu rada u zrakoplovu. Uključite Wi-Fi i Bluetooth ako ne želite da ostanu uključeni."
diff --git a/android/app/res/values-hu/strings.xml b/android/app/res/values-hu/strings.xml
index c14d9b008097c6ec207650f6902713e1a703bc6b..4b1c45193356d7fcf23e79e53d26bea9eacfa0cf 100644
--- a/android/app/res/values-hu/strings.xml
+++ b/android/app/res/values-hu/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audió"
"A 4 GB-nál nagyobb fájlokat nem lehet átvinni"
"Csatlakozás Bluetooth-eszközhöz"
+ "Bluetooth bekapcsolva Repülős üzemmódban"
+ "Ha bekapcsolva tartja a Bluetootht, a telefon emlékezni fog arra, hogy a következő alkalommal, amikor Repülős üzemmódban van, bekapcsolva tartsa a funkciót."
+ "A Bluetooth bekapcsolva marad"
+ "A telefon bekapcsolva tartja a Bluetootht Repülős üzemmódban. Kapcsolja ki a Bluetootht, ha nem szeretné, hogy bekapcsolva maradjon."
+ "A Wi-Fi és a Bluetooth bekapcsolva marad"
+ "A telefon bekapcsolva tartja a Wi‑Fi-t és a Bluetootht Repülős üzemmódban. Ha nem szeretné, hogy bekapcsolva maradjon a Wi-Fi és a Bluetooth, kapcsolja ki őket."
diff --git a/android/app/res/values-hy/strings.xml b/android/app/res/values-hy/strings.xml
index f0881ccf8361fe65e31fd992533e813db6f03bdb..28d1cabcab397ea9798732485f7425af59c01bc7 100644
--- a/android/app/res/values-hy/strings.xml
+++ b/android/app/res/values-hy/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth աուդիո"
"4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել"
"Միանալ Bluetooth-ին"
+ "Bluetooth-ը միացված է ավիառեժիմում"
+ "Եթե Bluetooth-ը միացված թողնեք, հաջորդ անգամ այն ավտոմատ միացված կմնա ավիառեժիմում"
+ "Bluetooth-ը կմնա միացված"
+ "Ավիառեժիմում Bluetooth-ը միացված կմնա։ Ցանկության դեպքում կարող եք անջատել Bluetooth-ը։"
+ "Wi-Fi-ը և Bluetooth-ը մնում են միացված"
+ "Ավիառեժիմում Wi-Fi-ը և Bluetooth-ը միացված կմնան։ Ցանկության դեպքում կարող եք անջատել Wi-Fi-ը և Bluetooth-ը։"
diff --git a/android/app/res/values-in/strings.xml b/android/app/res/values-in/strings.xml
index cf40fc6fef6a5434d0b8ed3433bb1a75c4bb6e3e..f9a8987ac95893ea088cfccff9471cb2444814a9 100644
--- a/android/app/res/values-in/strings.xml
+++ b/android/app/res/values-in/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"File yang berukuran lebih dari 4GB tidak dapat ditransfer"
"Hubungkan ke Bluetooth"
+ "Bluetooth aktif dalam mode pesawat"
+ "Jika Bluetooth tetap diaktifkan, ponsel akan ingat untuk tetap mengaktifkannya saat berikutnya ponsel Anda disetel ke mode pesawat"
+ "Bluetooth tetap aktif"
+ "Ponsel akan mengingat untuk tetap mengaktifkan Bluetooth dalam mode pesawat. Nonaktifkan jika Anda tidak ingin Bluetooth terus aktif."
+ "Wi-Fi dan Bluetooth tetap aktif"
+ "Ponsel akan mengingat untuk tetap mengaktifkan Wi-Fi dan Bluetooth dalam mode pesawat. Nonaktifkan jika Anda tidak ingin Wi-Fi dan Bluetooth terus aktif."
diff --git a/android/app/res/values-is/strings.xml b/android/app/res/values-is/strings.xml
index 5ca4de3d35ba55447192005dd5e92ee20a300bea..36f077f4a93ef83c6e1521e3890b1ae7cbc5eaaa 100644
--- a/android/app/res/values-is/strings.xml
+++ b/android/app/res/values-is/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-hljóð"
"Ekki er hægt að flytja skrár sem eru stærri en 4 GB"
"Tengjast við Bluetooth"
+ "Kveikt á Bluetooth í flugstillingu"
+ "Ef þú hefur kveikt á Bluetooth mun síminn muna að hafa kveikt á því næst þegar þú stillir á flugstillingu"
+ "Áfram kveikt á Bluetooth"
+ "Síminn man að hafa kveikt á Bluetooth í flugstillingu. Slökktu á Bluetooth ef þú vilt ekki hafa kveikt á því."
+ "Áfram verður kveikt á Wi-Fi og Bluetooth"
+ "Síminn man að hafa kveikt á Wi-Fi og Bluetooth í flugstillingu. Slökktu á Wi-Fi og Bluetooth ef þú vilt ekki hafa kveikt á þessu."
diff --git a/android/app/res/values-it/strings.xml b/android/app/res/values-it/strings.xml
index bab5e54100bf1797e64633e16f21571423f2bb8b..b9b8a6512139b94dd441b3b82e14cb769de79e8d 100644
--- a/android/app/res/values-it/strings.xml
+++ b/android/app/res/values-it/strings.xml
@@ -128,4 +128,10 @@
"Audio Bluetooth"
"Impossibile trasferire file con dimensioni superiori a 4 GB"
"Connettiti a Bluetooth"
+ "Bluetooth attivo in modalità aereo"
+ "Se tieni attivo il Bluetooth, il telefono ricorderà di tenerlo attivo la prossima volta che sarai in modalità aereo"
+ "Il Bluetooth rimane attivo"
+ "Il telefono memorizza che deve tenere attivo il Bluetooth in modalità aereo. Disattiva il Bluetooth se non vuoi tenerlo attivo."
+ "Wi-Fi e Bluetooth rimangono attivi"
+ "Il telefono memorizza che deve tenere attivi il Wi‑Fi e il Bluetooth in modalità aereo. Disattiva il Wi-Fi e il Bluetooth se non vuoi tenerli attivi."
diff --git a/android/app/res/values-iw/strings.xml b/android/app/res/values-iw/strings.xml
index ad24f441ba85c240bf056d995827a8926f130bde..51def833d8a26f33b092b746b126d28af00ef966 100644
--- a/android/app/res/values-iw/strings.xml
+++ b/android/app/res/values-iw/strings.xml
@@ -109,8 +109,8 @@
"כל הפריטים ינוקו מהרשימה."
"שיתוף Bluetooth: נשלחו קבצים"
"שיתוף Bluetooth: התקבלו קבצים"
- "{count,plural, =1{# נכשל}two{# נכשלו}many{# נכשלו}other{# נכשלו}}"
- "{count,plural, =1{# הצליח, %1$s}two{# הצליחו, %1$s}many{# הצליחו, %1$s}other{# הצליחו, %1$s}}"
+ "{count,plural, =1{# נכשל}one{# נכשלו}two{# נכשלו}other{# נכשלו}}"
+ "{count,plural, =1{# הצליח, %1$s}one{# הצליחו, %1$s}two{# הצליחו, %1$s}other{# הצליחו, %1$s}}"
"ניקוי רשימה"
"פתיחה"
"ניקוי מהרשימה"
@@ -128,4 +128,10 @@
"אודיו Bluetooth"
"לא ניתן להעביר קבצים שגדולים מ-4GB"
"התחברות באמצעות Bluetooth"
+ "חיבור ה-Bluetooth מופעל במצב טיסה"
+ "אם חיבור ה-Bluetooth נשאר מופעל, הטלפון יזכור להשאיר אותו מופעל בפעם הבאה שהוא יועבר למצב טיסה"
+ "Bluetooth יישאר מופעל"
+ "חיבור ה-Bluetooth בטלפון יישאר מופעל במצב טיסה. אפשר להשבית את ה-Bluetooth אם לא רוצים שהוא יפעל."
+ "חיבורי ה-Wi‑Fi וה-Bluetooth יישארו מופעלים"
+ "חיבורי ה-Wi‑Fi וה-Bluetooth בטלפון יישארו מופעלים במצב טיסה. אפשר להשבית את ה-Wi-Fi וה-Bluetooth אם לא רוצים שהם יפעלו."
diff --git a/android/app/res/values-ja/strings.xml b/android/app/res/values-ja/strings.xml
index 07089032caa4e6bb637471046f8ca3e010da5c85..fc4fe4ae2bbf3ff77ba28aea43df5f6659534900 100644
--- a/android/app/res/values-ja/strings.xml
+++ b/android/app/res/values-ja/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth オーディオ"
"4 GB を超えるファイルは転送できません"
"Bluetooth に接続する"
+ "機内モードで Bluetooth を ON にする"
+ "Bluetooth を ON にしておくと、次に機内モードになったときも ON のままになります"
+ "Bluetooth を ON にしておく"
+ "機内モードでも、スマートフォンの Bluetooth は ON のままになります。Bluetooth を ON にしたくない場合は OFF にしてください。"
+ "Wi-Fi と Bluetooth を ON のままにする"
+ "機内モードでも、スマートフォンの Wi-Fi と Bluetooth は ON のままになります。Wi-Fi と Bluetooth を ON にしたくない場合は OFF にしてください。"
diff --git a/android/app/res/values-ka/strings.xml b/android/app/res/values-ka/strings.xml
index 592eb4f7dae09832ed1f2fa0680e25ad896dea8c..42eb4e494e75b81adc7d8fb066b480a213e4036d 100644
--- a/android/app/res/values-ka/strings.xml
+++ b/android/app/res/values-ka/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth აუდიო"
"4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება"
"Bluetooth-თან დაკავშირება"
+ "Bluetooth ჩართულია თვითმფრინავის რეჟიმში"
+ "თუ Bluetooth-ს ჩართულს დატოვებთ, თქვენი ტელეფონი დაიმახსოვრებს და ჩართულს დატოვებს მას, როდესაც შემდეგ ჯერზე თვითმფრინავის რეჟიმში იქნებით"
+ "Bluetooth რᲩება Ჩართული"
+ "თქვენს ტელეფონს ემახსოვრება, რომ Bluetooth ჩართული უნდა იყოს თვითმფრინავის რეჟიმში. გამორთეთ Bluetooth, თუ არ გსურთ, რომ ის ჩართული იყოს."
+ "Wi-Fi და Bluetooth ჩართული დარჩება"
+ "თქვენს ტელეფონს ემახსოვრება, რომ Wi‑Fi და Bluetooth ჩართული უნდა იყოს თვითმფრინავის რეჟიმში. გამორთეთ Wi-Fi და Bluetooth, თუ არ გსურთ, რომ ისინი ჩართული იყოს."
diff --git a/android/app/res/values-kk/strings.xml b/android/app/res/values-kk/strings.xml
index 9913e3b12bd6177850c50ab3a41bb5ca0e94ed81..d807e04e7e010e006684c7d0fd0f8f69e2c38dc4 100644
--- a/android/app/res/values-kk/strings.xml
+++ b/android/app/res/values-kk/strings.xml
@@ -90,7 +90,7 @@
"Файлды аудару әлі басталған жоқ."
"Файлды аудару орындалуда."
"Файлды аудару сәтті орындалды."
- "Мазмұн қолдауы жоқ."
+ "Контент қолдауы жоқ."
"Аударуға қабылдайтын құрылғы тыйым салды."
"Тасымалды пайдаланушы тоқтатты."
"Жад ақаулығы."
@@ -128,4 +128,10 @@
"Bluetooth aудиосы"
"Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды"
"Bluetooth-қа қосылу"
+ "Bluetooth ұшақ режимінде қосулы"
+ "Bluetooth-ты қосулы қалдырсаңыз, келесі жолы ұшақ режиміне ауысқанда да ол қосылып тұрады."
+ "Bluetooth қосулы болады"
+ "Bluetooth ұшақ режимінде қосылып тұрады. Қаласаңыз, оны өшіріп қоюыңызға болады."
+ "Wi-Fi мен Bluetooth қосулы тұрады"
+ "Wi‑Fi мен Bluetooth ұшақ режимінде қосылып тұрады. Қаласаңыз, оларды өшіріп қоюыңызға болады."
diff --git a/android/app/res/values-km/strings.xml b/android/app/res/values-km/strings.xml
index abf646639db9156f00e56e06ca6c91e19cd89690..822765be43b1bf0b9771db87a27e140ccacbbf8e 100644
--- a/android/app/res/values-km/strings.xml
+++ b/android/app/res/values-km/strings.xml
@@ -128,4 +128,10 @@
"សំឡេងប៊្លូធូស"
"ឯកសារដែលមានទំហំធំជាង 4 GB មិនអាចផ្ទេរបានទេ"
"ភ្ជាប់ប៊្លូធូស"
+ "បើកប៊្លូធូសនៅក្នុងមុខងារពេលជិះយន្តហោះ"
+ "ប្រសិនបើអ្នកបើកប៊្លូធូស នោះទូរសព្ទរបស់អ្នកនឹងចាំថាត្រូវបើកវា នៅលើកក្រោយដែលអ្នកស្ថិតក្នុងមុខងារពេលជិះយន្តហោះ"
+ "ប៊្លូធូសបន្តបើក"
+ "ទូរសព្ទរបស់អ្នកចាំថាត្រូវបើកប៊្លូធូសនៅក្នុងមុខងារពេលជិះយន្តហោះ។ បិទប៊្លូធូស ប្រសិនបើអ្នកមិនចង់បើកទេ។"
+ "Wi-Fi និងប៊្លូធូសបន្តបើក"
+ "ទូរសព្ទរបស់អ្នកចាំថាត្រូវបើក Wi-Fi និងប៊្លូធូសនៅក្នុងមុខងារពេលជិះយន្តហោះ។ បិទ Wi-Fi និងប៊្លូធូស ប្រសិនបើអ្នកមិនចង់បើកទេ។"
diff --git a/android/app/res/values-kn/strings.xml b/android/app/res/values-kn/strings.xml
index 156f83a236edfb3873baeef66220b93d5e0a0b22..e9d376dcd6b6bc917c06f2fe11963e81fffb50b4 100644
--- a/android/app/res/values-kn/strings.xml
+++ b/android/app/res/values-kn/strings.xml
@@ -128,4 +128,10 @@
"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ"
"4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"
"ಬ್ಲೂಟೂತ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"
+ "ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿ ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿದೆ"
+ "ನೀವು ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರಿಸಿದರೆ, ಮುಂದಿನ ಬಾರಿ ನೀವು ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿರುವಾಗ ಅದನ್ನು ಆನ್ ಆಗಿರಿಸಿಕೊಳ್ಳುವುದನ್ನು ನಿಮ್ಮ ಫೋನ್ ನೆನಪಿಸಿಕೊಳ್ಳುತ್ತದೆ"
+ "ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರುತ್ತದೆ"
+ "ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿ ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರಿಸಿಕೊಳ್ಳುವುದನ್ನು ನಿಮ್ಮ ಫೋನ್ ನೆನಪಿಸಿಕೊಳ್ಳುತ್ತದೆ. ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರಿಸಲು ನೀವು ಬಯಸದಿದ್ದರೆ ಅದನ್ನು ಆಫ್ ಮಾಡಿ."
+ "ವೈ-ಫೈ ಮತ್ತು ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರುತ್ತದೆ"
+ "ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿ ವೈಫೈ ಮತ್ತು ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರಿಸಿಕೊಳ್ಳುವುದನ್ನು ನಿಮ್ಮ ಫೋನ್ ನೆನಪಿಸಿಕೊಳ್ಳುತ್ತದೆ. ವೈಫೈ ಮತ್ತು ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರಿಸಲು ನೀವು ಬಯಸದಿದ್ದರೆ ಅವುಗಳನ್ನು ಆಫ್ ಮಾಡಿ."
diff --git a/android/app/res/values-ko/strings.xml b/android/app/res/values-ko/strings.xml
index 964103ab71ca8c65625ade936a4536c842fc33e5..4a9c63fb9949b4984cdc04ccbd4e80fa32545e0c 100644
--- a/android/app/res/values-ko/strings.xml
+++ b/android/app/res/values-ko/strings.xml
@@ -128,4 +128,10 @@
"블루투스 오디오"
"4GB보다 큰 파일은 전송할 수 없습니다"
"블루투스에 연결"
+ "비행기 모드에서 블루투스 사용 설정"
+ "블루투스를 켜진 상태로 유지하면 다음에 비행기 모드를 사용할 때도 블루투스 연결이 유지됩니다."
+ "블루투스가 켜진 상태로 유지됨"
+ "휴대전화가 비행기 모드에서 블루투스를 켜진 상태로 유지합니다. 유지하지 않으려면 블루투스를 사용 중지하세요."
+ "Wi-Fi 및 블루투스 계속 사용"
+ "휴대전화가 비행기 모드에서 Wi-Fi 및 블루투스를 켜진 상태로 유지합니다. 유지하지 않으려면 Wi-Fi와 블루투스를 사용 중지하세요."
diff --git a/android/app/res/values-ky/strings.xml b/android/app/res/values-ky/strings.xml
index 3aa9af4c081c80e70d8068c112f0940b43b4ea18..954556f3a4aa4ee8141128f3a53c63ada6e4bfe7 100644
--- a/android/app/res/values-ky/strings.xml
+++ b/android/app/res/values-ky/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth аудио"
"4Гб чоң файлдарды өткөрүү мүмкүн эмес"
"Bluetooth\'га туташуу"
+ "Учак режиминде Bluetooth күйүк"
+ "Эгер Bluetooth күйүк бойдон калса, кийинки жолу учак режимине өткөнүңүздө телефонуңуз аны эстеп калат"
+ "Bluetooth күйүк бойдон калат"
+ "Телефонуңуз учак режиминде Bluetooth\'га туташкан бойдон калат. Кааласаңыз, Bluetooth\'ду өчүрүп койсоңуз болот."
+ "Wi-Fi менен Bluetooth күйүк бойдон калат"
+ "Телефонуңуз учак режиминде Wi‑Fi\'га жана Bluetooth\'га туташкан бойдон калат. Кааласаңыз, Wi-Fi менен Bluetooth\'ду өчүрүп койсоңуз болот."
diff --git a/android/app/res/values-lo/strings.xml b/android/app/res/values-lo/strings.xml
index b1eb0966e3481bb9c2524b6dd5d8eacb3eed36d7..0dcbeb77e7df9d2b8b0a5e406dbd3f05db703cfa 100644
--- a/android/app/res/values-lo/strings.xml
+++ b/android/app/res/values-lo/strings.xml
@@ -128,4 +128,10 @@
"ສຽງ Bluetooth"
"ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້"
"ເຊື່ອມຕໍ່ກັບ Bluetooth"
+ "ເປີດ Bluetooth ໃນໂໝດຢູ່ໃນຍົນ"
+ "ຫາກທ່ານເປີດ Bluetooth ປະໄວ້, ໂທລະສັບຂອງທ່ານຈະຈື່ວ່າຕ້ອງເປີດ Wi‑Fi ໃນເທື່ອຕໍ່ໄປທີ່ທ່ານຢູ່ໃນໂໝດຢູ່ໃນຍົນ"
+ "Bluetooth ເປີດຢູ່"
+ "ໂທລະສັບຂອງທ່ານຈື່ວ່າຈະຕ້ອງເປີດ Bluetooth ປະໄວ້ໃນໂໝດຢູ່ໃນຍົນ. ປິດ Bluetooth ຫາກທ່ານບໍ່ຕ້ອງການໃຫ້ເປີດປະໄວ້."
+ "Wi-Fi ແລະ Bluetooth ຈະເປີດປະໄວ້"
+ "ໂທລະສັບຂອງທ່ານຈື່ວ່າຈະຕ້ອງເປີດ Wi-Fi ແລະ Bluetooth ປະໄວ້ໃນໂໝດຢູ່ໃນຍົນ. ປິດ Wi-Fi ແລະ Bluetooth ຫາກທ່ານບໍ່ຕ້ອງການໃຫ້ເປີດປະໄວ້."
diff --git a/android/app/res/values-lt/strings.xml b/android/app/res/values-lt/strings.xml
index d29f1033f66dbc22ad7c1b97198c8f7677cc4659..f7972c0d559146c741d392697e3e425e5c68059d 100644
--- a/android/app/res/values-lt/strings.xml
+++ b/android/app/res/values-lt/strings.xml
@@ -128,4 +128,10 @@
"„Bluetooth“ garsas"
"Negalima perkelti didesnių nei 4 GB failų"
"Prisijungti prie „Bluetooth“"
+ "„Bluetooth“ ryšys įjungtas lėktuvo režimu"
+ "Jei paliksite „Bluetooth“ ryšį įjungtą, telefonas, prisimins palikti jį įjungtą, kai kitą kartą įjungsite lėktuvo režimą"
+ "„Bluetooth“ liks įjungtas"
+ "Telefonas prisimena, kad naudojant lėktuvo režimą reikia palikti įjungtą „Bluetooth“ ryšį. Išjunkite „Bluetooth“, jei nenorite, kad jis liktų įjungtas."
+ "„Wi‑Fi“ ir „Bluetooth“ ryšys lieka įjungtas"
+ "Telefonas prisimena, kad lėktuvo režimu reikia palikti įjungtą „Wi‑Fi“ ir „Bluetooth“ ryšį. Išjunkite „Wi-Fi“ ir „Bluetooth“, jei nenorite, kad jie liktų įjungti."
diff --git a/android/app/res/values-lv/strings.xml b/android/app/res/values-lv/strings.xml
index a250dcff4d2407cf7aa46206d2ce36425e572745..9f8282336a42df5fc2944f965ffaf0691be2df6f 100644
--- a/android/app/res/values-lv/strings.xml
+++ b/android/app/res/values-lv/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth audio"
"Nevar pārsūtīt failus, kas lielāki par 4 GB."
"Izveidot savienojumu ar Bluetooth"
+ "Tehnoloģija Bluetooth lidojuma režīmā paliek ieslēgta"
+ "Ja Bluetooth savienojums paliks ieslēgts, tālrunī tas paliks ieslēgts arī nākamreiz, kad ieslēgsiet lidojuma režīmu."
+ "Bluetooth savienojums joprojām ir ieslēgts"
+ "Lidojuma režīmā tālrunī joprojām būs ieslēgts Bluetooth savienojums. Izslēdziet Bluetooth savienojumu, ja nevēlaties, lai tas paliktu ieslēgts."
+ "Wi-Fi savienojums un tehnoloģija Bluetooth paliek ieslēgta"
+ "Lidojuma režīmā tālrunī joprojām būs ieslēgti Wi-Fi un Bluetooth savienojumi. Izslēdziet Wi-Fi un Bluetooth savienojumus, ja nevēlaties, lai tie paliktu ieslēgti."
diff --git a/android/app/res/values-mk/strings.xml b/android/app/res/values-mk/strings.xml
index ee0b2007f6bebec4db232d6efc019624875170b1..b580d37787d3c755c882d58fea75a7b6fe50fffd 100644
--- a/android/app/res/values-mk/strings.xml
+++ b/android/app/res/values-mk/strings.xml
@@ -128,4 +128,10 @@
"Аудио преку Bluetooth"
"Не може да се пренесуваат датотеки поголеми од 4 GB"
"Поврзи се со Bluetooth"
+ "Вклучен Bluetooth во авионски режим"
+ "Ако го оставите Bluetooth вклучен, телефонот ќе запомни да го остави вклучен до следниот пат кога ќе бидете во авионски режим"
+ "Bluetooth останува вклучен"
+ "Телефонот помни да го задржи Bluetooth вклучен во авионски режим. Исклучете го Bluetooth ако не сакате да остане вклучен."
+ "Wi-Fi и Bluetooth остануваат вклучени"
+ "Телефонот помни да ги задржи Wi‑Fi и Bluetooth вклучени во авионски режим. Исклучете ги Wi-Fi и Bluetooth ако не сакате да бидат вклучени."
diff --git a/android/app/res/values-ml/strings.xml b/android/app/res/values-ml/strings.xml
index 25f1a128ac17cfd2c120354e54080bb27c2cc57c..e511d2c25935efd3617376fbf3dffefeab927853 100644
--- a/android/app/res/values-ml/strings.xml
+++ b/android/app/res/values-ml/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth ഓഡിയോ"
"4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല"
"Bluetooth-ലേക്ക് കണക്റ്റ് ചെയ്യുക"
+ "ഫ്ലൈറ്റ് മോഡിൽ Bluetooth ഓണാണ്"
+ "Bluetooth ഓണാക്കി വച്ചാൽ, അടുത്ത തവണ നിങ്ങൾ ഫ്ലൈറ്റ് മോഡിൽ ആയിരിക്കുമ്പോൾ നിങ്ങളുടെ ഫോൺ അത് ഓണാക്കി വയ്ക്കാൻ ഓർക്കും"
+ "Bluetooth ഓണാക്കിയ നിലയിൽ തുടരും"
+ "ഫ്ലൈറ്റ് മോഡിലായിരിക്കുമ്പോൾ Bluetooth ഓണാക്കി വയ്ക്കാൻ നിങ്ങളുടെ ഫോൺ ഓർമ്മിക്കുന്നു. Bluetooth ഓണാക്കി വയ്ക്കാൻ താൽപ്പര്യമില്ലെങ്കിൽ അത് ഓഫാക്കുക."
+ "വൈഫൈ, Bluetooth എന്നിവ ഓണായ നിലയിൽ തുടരും"
+ "ഫ്ലൈറ്റ് മോഡിലായിരിക്കുമ്പോൾ വൈഫൈ, Bluetooth എന്നിവ ഓണാക്കി വയ്ക്കാൻ നിങ്ങളുടെ ഫോൺ ഓർമ്മിക്കുന്നു. വൈഫൈ, Bluetooth എന്നിവ ഓണാക്കി വയ്ക്കാൻ താൽപ്പര്യമില്ലെങ്കിൽ അവ ഓഫാക്കുക."
diff --git a/android/app/res/values-mn/strings.xml b/android/app/res/values-mn/strings.xml
index 6eaec121ecaf542ee6fbde9d999a1446bfad171f..0eb7701e87752638f1b7fbdc750ff29f926dd279 100644
--- a/android/app/res/values-mn/strings.xml
+++ b/android/app/res/values-mn/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Аудио"
"4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй"
"Bluetooth-тэй холбогдох"
+ "Нислэгийн горимд Bluetooth асаалттай"
+ "Хэрэв та Bluetooth-г асаалттай байлгавал таныг дараагийн удаа нислэгийн горимд байх үед утас тань үүнийг асаалттай байлгахыг санана"
+ "Bluetooth асаалттай хэвээр байна"
+ "Таны утас Bluetooth-г нислэгийн горимд асаалттай байлгахыг санана. Хэрэв та асаалттай байлгахыг хүсэхгүй байгаа бол Bluetooth-г унтрааж болно."
+ "Wi-Fi болон Bluetooth асаалттай хэвээр байна"
+ "Таны утас Wi-Fi болон Bluetooth-г нислэгийн горимд асаалттай байлгахыг санана. Хэрэв та асаалттай байлгахыг хүсэхгүй байгаа бол Wi-Fi болон Bluetooth-г унтрааж болно."
diff --git a/android/app/res/values-mr/strings.xml b/android/app/res/values-mr/strings.xml
index a4174e3736f1c3a940b4640ddbd281ec94b30c47..ade07591772bfd106fda8486faeb1feaa2742b54 100644
--- a/android/app/res/values-mr/strings.xml
+++ b/android/app/res/values-mr/strings.xml
@@ -90,7 +90,7 @@
"फाइल स्थानांतरणाचा अद्याप सुरू झाला नाही."
"फाइल स्थानांतर सुरू आहे."
"फाइल स्थानांतरण यशस्वीरित्या पूर्ण झाले."
- "आशय समर्थित नाही."
+ "आशयाला सपोर्ट नाही."
"लक्ष्य डिव्हाइसद्वारे स्थानांतरण निषिद्ध केले."
"वापरकर्त्याने स्थानांतरण रद्द केले."
"संचयन समस्या."
@@ -128,4 +128,10 @@
"ब्लूटूथ ऑडिओ"
"4 GB हून मोठ्या फाइल ट्रान्सफर करता येणार नाहीत"
"ब्लूटूथशी कनेक्ट करा"
+ "विमान मोडमध्ये ब्लूटूथ सुरू आहे"
+ "तुम्ही ब्लूटूथ सुरू ठेवल्यास, पुढील वेळी विमान मोडमध्ये असाल, तेव्हा तुमचा फोन ते सुरू ठेवण्याचे लक्षात ठेवेल"
+ "ब्लूटूथ सुरू राहते"
+ "तुमचा फोन विमान मोडमध्ये ब्लूटूथ सुरू ठेवण्याचे लक्षात ठेवतो. तुम्हाला ब्लूटूथ सुरू ठेवायचे नसल्यास ते बंद करा."
+ "वाय-फाय आणि ब्लूटूथ सुरू राहते"
+ "तुमचा फोन विमान मोडमध्ये वाय-फाय आणि ब्लूटूथ सुरू ठेवण्याचे लक्षात ठेवतो. तुम्हाला वाय-फाय आणि ब्लूटूथ सुरू ठेवायचे नसल्यास ते बंद करा."
diff --git a/android/app/res/values-ms/strings.xml b/android/app/res/values-ms/strings.xml
index b79a9c3cd904228cda2c4ed2019ef12e7accc070..07d1f3acef7b0cd7af9ecdbcfdfb5862b3a1b07c 100644
--- a/android/app/res/values-ms/strings.xml
+++ b/android/app/res/values-ms/strings.xml
@@ -128,4 +128,10 @@
"Audio Bluetooth"
"Fail lebih besar daripada 4GB tidak boleh dipindahkan"
"Sambung ke Bluetooth"
+ "Bluetooth dihidupkan dalam mod pesawat"
+ "Jika anda terus menghidupkan Bluetooth, telefon anda akan ingat untuk membiarkan Bluetooth hidup pada kali seterusnya telefon anda berada dalam mod pesawat"
+ "Bluetooth kekal dihidupkan"
+ "Telefon anda diingatkan untuk terus menghidupkan Bluetooth dalam mod pesawat. Matikan Bluetooth jika anda tidak mahu Bluetooth sentiasa hidup."
+ "Wi-Fi dan Bluetooth kekal dihidupkan"
+ "Telefon anda diingatkan untuk terus menghidupkan Wi-Fi dan Bluetooth dalam mod pesawat. Matikan Wi-Fi dan Bluetooth jika anda tidak mahu Wi-Fi dan Bluetooth sentiasa hidup."
diff --git a/android/app/res/values-my/strings.xml b/android/app/res/values-my/strings.xml
index 692d1c03b9f0257db5d361a0e62f0db50987ff6f..b7c523df8729a62ff8deaa65d12d72ad2c39028f 100644
--- a/android/app/res/values-my/strings.xml
+++ b/android/app/res/values-my/strings.xml
@@ -128,4 +128,10 @@
"ဘလူးတုသ် အသံ"
"4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ"
"ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်"
+ "လေယာဉ်ပျံမုဒ်တွင် ဘလူးတုသ် ပွင့်နေသည်"
+ "ဘလူးတုသ် ဆက်ဖွင့်ထားပါက နောက်တစ်ကြိမ်လေယာဉ်ပျံမုဒ် သုံးချိန်တွင် ၎င်းဆက်ဖွင့်ရန် သင့်ဖုန်းက မှတ်ထားမည်။"
+ "ဘလူးတုသ် ဆက်ပွင့်နေသည်"
+ "လေယာဉ်ပျံမုဒ်သုံးစဉ် ဘလူးတုသ် ဆက်ဖွင့်ထားရန် သင့်ဖုန်းက မှတ်မိသည်။ ဘလူးတုသ် ဆက်ဖွင့်မထားလိုပါက ပိတ်နိုင်သည်။"
+ "Wi-Fi နှင့် ဘလူးတုသ် ဆက်ဖွင့်ထားသည်"
+ "လေယာဉ်ပျံမုဒ်သုံးစဉ် Wi-Fi နှင့် ဘလူးတုသ် ဆက်ဖွင့်ထားရန် သင့်ဖုန်းက မှတ်မိသည်။ Wi-Fi နှင့် ဘလူးတုသ် ဆက်ဖွင့်မထားလိုပါက ပိတ်နိုင်သည်။"
diff --git a/android/app/res/values-nb/strings.xml b/android/app/res/values-nb/strings.xml
index 91f1b1fb03eb7d8cd1d62b3cfe3cca215f2fafc5..8c975818bb588350cf16337737235524f6eb438c 100644
--- a/android/app/res/values-nb/strings.xml
+++ b/android/app/res/values-nb/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-lyd"
"Filer som er større enn 4 GB, kan ikke overføres"
"Koble til Bluetooth"
+ "Bluetooth er på i flymodus"
+ "Hvis du lar Bluetooth være på, husker telefonen dette til den neste gangen du bruker flymodus"
+ "Bluetooth blir værende på"
+ "Telefonen husker at Bluetooth skal være på i flymodus. Slå av Bluetooth hvis du ikke vil at det skal være på."
+ "Wifi og Bluetooth holdes påslått"
+ "Telefonen husker at wifi og Bluetooth skal være på i flymodus. Slå av wifi og Bluetooth hvis du ikke vil at de skal være på."
diff --git a/android/app/res/values-ne/strings.xml b/android/app/res/values-ne/strings.xml
index a2b029a9442ba67f61e14e664b9b1468d2a3ce5d..67e0bf8c828d01f5fdb6f29923e6add477f42879 100644
--- a/android/app/res/values-ne/strings.xml
+++ b/android/app/res/values-ne/strings.xml
@@ -128,4 +128,10 @@
"ब्लुटुथको अडियो"
"४ जि.बि. भन्दा ठूला फाइलहरूलाई स्थानान्तरण गर्न सकिँदैन"
"ब्लुटुथमा कनेक्ट गर्नुहोस्"
+ "हवाइजहाज मोडमा ब्लुटुथ अन राखियोस्"
+ "तपाईंले ब्लुटुथ अन राखिराख्नुभयो भने तपाईंले आफ्नो फोन अर्को पटक हवाइजहाज मोडमा लैजाँदा तपाईंको फोनले ब्लुटुथ अन राख्नु पर्ने कुरा याद गर्छ"
+ "ब्लुटुथ अन रहन्छ"
+ "तपाईंको फोन अर्को पटक हवाइजहाज मोडमा लैजाँदा तपाईंको फोनले ब्लुटुथ अन राख्नु पर्ने कुरा याद गर्छ तपाईं ब्लुटुथ अन भइनरहोस् भन्ने चाहनुहुन्छ भने ब्लुटुथ अफ गर्नुहोस्।"
+ "Wi-Fi र ब्लुटुथ अन रहिरहने छन्"
+ "हवाइजहाज मोडमा पनि तपाईंको फोनको Wi-Fi र ब्लुटुथ अन नै रहिरहने छन्। तपाईं Wi-Fi र ब्लुटुथ अन भइनरहोस् भन्ने चाहनुहुन्छ भने तिनलाई अफ गर्नुहोस्।"
diff --git a/android/app/res/values-nl/strings.xml b/android/app/res/values-nl/strings.xml
index f8cfb8bad50163eb91a290d4b1052216b5f4c3d8..83493b168c35c64c8513001c001d3754e3eea719 100644
--- a/android/app/res/values-nl/strings.xml
+++ b/android/app/res/values-nl/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-audio"
"Bestanden groter dan 4 GB kunnen niet worden overgedragen"
"Verbinding maken met bluetooth"
+ "Bluetooth aan in de vliegtuigmodus"
+ "Als je bluetooth laat aanstaan, onthoudt je telefoon dit en blijft bluetooth aanstaan als je de vliegtuigmodus weer aanzet"
+ "Bluetooth blijft aan"
+ "Bluetooth op je telefoon blijft aan in de vliegtuigmodus. Zet bluetooth uit als je niet wilt dat dit aan blijft."
+ "Wifi en bluetooth blijven aan"
+ "Wifi en bluetooth op je telefoon blijven aan in de vliegtuigmodus. Zet wifi en bluetooth uit als je niet wilt dat ze aan blijven."
diff --git a/android/app/res/values-or/strings.xml b/android/app/res/values-or/strings.xml
index 206bde29156774fd870a2ad4bd5151e7dcfa4827..66ecc7d59e81af2959e17b02df852d75b96683fc 100644
--- a/android/app/res/values-or/strings.xml
+++ b/android/app/res/values-or/strings.xml
@@ -19,7 +19,7 @@
"ଡାଉନଲୋଡ ମ୍ୟାନେଜର୍କୁ ଆକ୍ସେସ୍ କରନ୍ତୁ।"
"BluetoothShare ମ୍ୟାନେଜର୍ ଆକ୍ସେସ୍ କରି ଫାଇଲ୍ଗୁଡ଼ିକ ଟ୍ରାନ୍ସଫର୍ କରିବାକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"
"ବ୍ଲୁଟୁଥ୍ ଡିଭାଇସର ଆକ୍ସେସକୁ ଗ୍ରହଣ-ସୂଚୀରେ ରଖନ୍ତୁ।"
- "ଏକ ବ୍ଲୁଟୁଥ୍ ଡିଭାଇସକୁ ଅସ୍ଥାୟୀ ଭାବେ ଗ୍ରହଣ-ସୂଚୀରେ ରଖିବାକୁ ଆପଟିକୁ ଅନୁମତି ଦେଇଥାଏ, ଯାହା ଦ୍ୱାରା ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ସୁନିଶ୍ଚିତକରଣ ବିନା ଏହି ଡିଭାଇସକୁ ଫାଇଲ୍ ପଠାଇବା ପାଇଁ ସେହି ଡିଭାଇସକୁ ଅନୁମତି ମିଳିଥାଏ।"
+ "ଏକ ବ୍ଲୁଟୁଥ ଡିଭାଇସକୁ ଅସ୍ଥାୟୀ ଭାବେ ଗ୍ରହଣ-ସୂଚୀରେ ରଖିବାକୁ ଆପଟିକୁ ଅନୁମତି ଦେଇଥାଏ, ଯାହା ଦ୍ୱାରା ୟୁଜରଙ୍କ ସୁନିଶ୍ଚିତକରଣ ବିନା ଏହି ଡିଭାଇସକୁ ଫାଇଲ ପଠାଇବା ପାଇଁ ସେହି ଡିଭାଇସକୁ ଅନୁମତି ମିଳିଥାଏ।"
"ବ୍ଲୁଟୁଥ"
"ଅଜଣା ଡିଭାଇସ୍"
"ଅଜଣା"
@@ -92,7 +92,7 @@
"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ସଫଳତାପୂର୍ବକ ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"
"କଣ୍ଟେଣ୍ଟ ସପୋର୍ଟ କରୁନାହିଁ।"
"ଟାର୍ଗେଟ୍ ଡିଭାଇସ୍ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ ପ୍ରତିବନ୍ଧିତ କରାଯାଇଛି।"
- "ୟୁଜର୍ଙ୍କ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ କ୍ୟାନ୍ସଲ୍ କରାଗଲା।"
+ "ୟୁଜରଙ୍କ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର କେନ୍ସଲ କରାଯାଇଛି।"
"ଷ୍ଟୋରେଜ୍ ସମସ୍ୟା।"
"କୌଣସି USB ଷ୍ଟୋରେଜ୍ ନାହିଁ।"
"କୌଣସି SD କାର୍ଡ ନାହିଁ। ଟ୍ରାନ୍ସଫର୍ କରାଯାଇଥିବା ଫାଇଲ୍ଗୁଡ଼ିକୁ ସେଭ୍ କରିବା ପାଇଁ ଗୋଟିଏ SD କାର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ।"
@@ -128,4 +128,10 @@
"ବ୍ଲୁଟୂଥ୍ ଅଡିଓ"
"4GBରୁ ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍ କରାଯାଇପାରିବ ନାହିଁ"
"ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ"
+ "ଏୟାରପ୍ଲେନ ମୋଡରେ ବ୍ଲୁଟୁଥ ଚାଲୁ ଅଛି"
+ "ଯଦି ଆପଣ ବ୍ଲୁଟୁଥକୁ ଚାଲୁ ରଖନ୍ତି, ତେବେ ଆପଣ ପରବର୍ତ୍ତୀ ଥର ଏୟାରପ୍ଲେନ ମୋଡରେ ଥିବା ସମୟରେ ଆପଣଙ୍କ ଫୋନ ଏହାକୁ ଚାଲୁ ରଖିବା ପାଇଁ ମନେ ରଖିବ"
+ "ବ୍ଲୁଟୁଥ ଚାଲୁ ରହେ"
+ "ଆପଣଙ୍କ ଫୋନ ଏୟାରପ୍ଲେନ ମୋଡରେ ବ୍ଲୁଟୁଥକୁ ଚାଲୁ ରଖିବା ପାଇଁ ମନେ ରଖେ। ଯଦି ଆପଣ ବ୍ଲୁଟୁଥ ଚାଲୁ ରଖିବାକୁ ଚାହାଁନ୍ତି ନାହିଁ ତେବେ ଏହାକୁ ବନ୍ଦ କରନ୍ତୁ।"
+ "ୱାଇ-ଫାଇ ଏବଂ ବ୍ଲୁଟୁଥ ଚାଲୁ ରହେ"
+ "ଆପଣଙ୍କ ଫୋନ ଏୟାରପ୍ଲେନ ମୋଡରେ ୱାଇ-ଫାଇ ଏବଂ ବ୍ଲୁଟୁଥକୁ ଚାଲୁ ରଖିବା ପାଇଁ ମନେ ରଖେ। ଯଦି ଆପଣ ୱାଇ-ଫାଇ ଏବଂ ବ୍ଲୁଟୁଥ ଚାଲୁ ରଖିବାକୁ ଚାହାଁନ୍ତି ନାହିଁ ତେବେ ସେଗୁଡ଼ିକୁ ବନ୍ଦ କରନ୍ତୁ।"
diff --git a/android/app/res/values-or/strings_pbap.xml b/android/app/res/values-or/strings_pbap.xml
index 40c1e7f4d25df4e81f3d98586851384208267762..914f58745d967fb2089b0b20632a35cf68f03f43 100644
--- a/android/app/res/values-or/strings_pbap.xml
+++ b/android/app/res/values-or/strings_pbap.xml
@@ -12,5 +12,5 @@
"ଅଜଣା ନାମ"
"ମୋର ନାମ"
"000000"
- "ବ୍ଲୁଟୂଥ୍ ଯୋଗାଯୋଗ ସେୟାର୍ କରନ୍ତୁ"
+ "ବ୍ଲୁଟୂଥ କଣ୍ଟାକ୍ଟ ସେୟାର କରନ୍ତୁ"
diff --git a/android/app/res/values-pa/strings.xml b/android/app/res/values-pa/strings.xml
index 8c0f2918f95d648fdcf4487273eda8d8385d4f92..13570e16b289b30b30bdbbf777223f29d652f151 100644
--- a/android/app/res/values-pa/strings.xml
+++ b/android/app/res/values-pa/strings.xml
@@ -128,4 +128,10 @@
"ਬਲੂਟੁੱਥ ਆਡੀਓ"
"4GB ਤੋਂ ਜ਼ਿਆਦਾ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"
"ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"
+ "ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ ਵਿੱਚ ਬਲੂਟੁੱਥ ਚਾਲੂ ਹੈ"
+ "ਜੇ ਤੁਸੀਂ ਬਲੂਟੁੱਥ ਨੂੰ ਚਾਲੂ ਰੱਖਦੇ ਹੋ, ਤਾਂ ਅਗਲੀ ਵਾਰ ਤੁਹਾਡਾ ਫ਼ੋਨ ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ ਵਿੱਚ ਹੋਣ \'ਤੇ ਤੁਹਾਡਾ ਫ਼ੋਨ ਇਸਨੂੰ ਚਾਲੂ ਰੱਖਣਾ ਯਾਦ ਰੱਖੇਗਾ"
+ "ਬਲੂਟੁੱਥ ਚਾਲੂ ਰਹਿੰਦਾ ਹੈ"
+ "ਤੁਹਾਡਾ ਫ਼ੋਨ ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ ਵਿੱਚ ਬਲੂਟੁੱਥ ਨੂੰ ਚਾਲੂ ਰੱਖਣਾ ਯਾਦ ਰੱਖਦਾ ਹੈ। ਜੇ ਤੁਸੀਂ ਇਸਨੂੰ ਚਾਲੂ ਨਹੀਂ ਰੱਖਣਾ ਚਾਹੁੰਦੇ, ਤਾਂ ਬਲੂਟੁੱਥ ਨੂੰ ਬੰਦ ਕਰੋ।"
+ "ਵਾਈ-ਫਾਈ ਅਤੇ ਬਲੂਟੁੱਥ ਚਾਲੂ ਰਹਿੰਦੇ ਹਨ"
+ "ਤੁਹਾਡਾ ਫ਼ੋਨ ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ ਵਿੱਚ ਵਾਈ-ਫਾਈ ਅਤੇ ਬਲੂਟੁੱਥ ਨੂੰ ਚਾਲੂ ਰੱਖਣਾ ਯਾਦ ਰੱਖਦਾ ਹੈ। ਜੇ ਤੁਸੀਂ ਇਨ੍ਹਾਂ ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਰੱਖਣਾ ਚਾਹੁੰਦੇ, ਤਾਂ ਵਾਈ-ਫਾਈ ਅਤੇ ਬਲੂਟੁੱਥ ਨੂੰ ਬੰਦ ਕਰੋ।"
diff --git a/android/app/res/values-pl/strings.xml b/android/app/res/values-pl/strings.xml
index 129805a276629a770d05c5bcbb3cf7e9a77377af..eca2def4a8838b1a6c2f99955b3fcb37d8224c6b 100644
--- a/android/app/res/values-pl/strings.xml
+++ b/android/app/res/values-pl/strings.xml
@@ -128,4 +128,10 @@
"Dźwięk Bluetooth"
"Nie można przenieść plików przekraczających 4 GB"
"Nawiązywanie połączeń przez Bluetooth"
+ "Bluetooth włączony w trybie samolotowym"
+ "Jeśli pozostawisz włączony Bluetooth, telefon zachowa się podobnie przy kolejnym przejściu w tryb samolotowy"
+ "Bluetooth pozostanie włączony"
+ "Bluetooth na telefonie pozostaje włączony w trybie samolotowym. Wyłącz Bluetooth, jeśli nie chcesz, żeby pozostawał włączony."
+ "Wi-Fi i Bluetooth pozostają włączone"
+ "Wi-Fi i Bluetooth na telefonie pozostają włączone w trybie samolotowym. Wyłącz Wi-Fi i Bluetooth, jeśli nie chcesz, żeby funkcje te pozostawały włączone."
diff --git a/android/app/res/values-pt-rPT/strings.xml b/android/app/res/values-pt-rPT/strings.xml
index c2c09c3013daa353c8225ddce76e85e43e65d716..4469a011daa985b6ecc2b95dea553acf6130b09d 100644
--- a/android/app/res/values-pt-rPT/strings.xml
+++ b/android/app/res/values-pt-rPT/strings.xml
@@ -86,7 +86,7 @@
"Não existe espaço suficiente na memória USB para guardar o ficheiro."
"Não existe espaço suficiente no cartão SD para guardar o ficheiro."
"Espaço necessário: %1$s"
- "Existem demasiados pedidos em processamento. Tente novamente mais tarde."
+ "Existem demasiados pedidos em processamento. Tente mais tarde."
"Ainda não foi iniciada a transferência do ficheiro."
"Transferência do ficheiro em curso."
"Transferência de ficheiros concluída com êxito."
@@ -128,4 +128,10 @@
"Áudio Bluetooth"
"Não é possível transferir os ficheiros com mais de 4 GB."
"Ligar ao Bluetooth"
+ "Bluetooth ativado no modo de avião"
+ "Se mantiver o Bluetooth ativado, o telemóvel vai lembrar-se de o manter ativado da próxima vez que estiver no modo de avião"
+ "O Bluetooth mantém-se ativado"
+ "O seu telemóvel mantém o Bluetooth ativado no modo de avião. Desative o Bluetooth se não quiser que fique ativado."
+ "O Wi-Fi e o Bluetooth mantêm-se ativados"
+ "O seu telemóvel mantém o Wi-Fi e o Bluetooth ativados no modo de avião. Desative o Wi-Fi e o Bluetooth se não quiser que fiquem ativados."
diff --git a/android/app/res/values-pt/strings.xml b/android/app/res/values-pt/strings.xml
index 8473c6009dbfe9ed692f49f21df5ace98630e66e..612ef7ccbee97d04de16212d2c6e0304b57a41c0 100644
--- a/android/app/res/values-pt/strings.xml
+++ b/android/app/res/values-pt/strings.xml
@@ -128,4 +128,10 @@
"Áudio Bluetooth"
"Não é possível transferir arquivos maiores que 4 GB"
"Conectar ao Bluetooth"
+ "Bluetooth ativado no modo avião"
+ "Se você escolher manter o Bluetooth ativado, essa configuração vai ser aplicada na próxima vez que usar o modo avião"
+ "O Bluetooth fica ativado"
+ "O smartphone vai manter o Bluetooth ativado no modo avião. Ele pode ser desativado manualmente se você preferir."
+ "O Wi-Fi e o Bluetooth ficam ativados"
+ "O smartphone vai manter o Wi-Fi e o Bluetooth ativados no modo avião. Eles podem ser desativados manualmente se você preferir."
diff --git a/android/app/res/values-ro/strings.xml b/android/app/res/values-ro/strings.xml
index c987e6dae2537f7c5a1f99792e8313c16fe0eb2c..61bfb10cdac81528a37acd80f7fc9cc14fb4f4aa 100644
--- a/android/app/res/values-ro/strings.xml
+++ b/android/app/res/values-ro/strings.xml
@@ -128,4 +128,10 @@
"Audio prin Bluetooth"
"Fișierele mai mari de 4 GB nu pot fi transferate"
"Conectează-te la Bluetooth"
+ "Bluetooth activat în modul Avion"
+ "Dacă păstrezi Bluetooth activat, telefonul tău va reține să-l păstreze activat data viitoare când ești în modul Avion"
+ "Bluetooth rămâne activat"
+ "Telefonul reține să păstreze Bluetooth activat în modul Avion. Dezactivează Bluetooth dacă nu vrei să rămână activat."
+ "Wi-Fi și Bluetooth rămân activate"
+ "Telefonul reține să păstreze funcțiile Wi-Fi și Bluetooth activate în modul Avion. Dezactivează Wi-Fi și Bluetooth dacă nu vrei să rămână activate."
diff --git a/android/app/res/values-ru/strings.xml b/android/app/res/values-ru/strings.xml
index 7f206e40a546c13d997639b621cd78b7a49f755c..d1e42c858283420046a55194b37efc33656b8500 100644
--- a/android/app/res/values-ru/strings.xml
+++ b/android/app/res/values-ru/strings.xml
@@ -128,4 +128,10 @@
"Звук через Bluetooth"
"Можно перенести только файлы размером до 4 ГБ."
"Подключиться по Bluetooth"
+ "Функция Bluetooth будет включена в режиме полета"
+ "Если не отключить функцию Bluetooth, в следующий раз она останется включенной в режиме полета."
+ "Функция Bluetooth остается включенной"
+ "Функция Bluetooth останется включенной в режиме полета. Вы можете отключить ее, если хотите."
+ "Функции Wi‑Fi и Bluetooth остаются включенными"
+ "Wi‑Fi и Bluetooth останутся включенными в режиме полета. Вы можете отключить их, если хотите."
diff --git a/android/app/res/values-si/strings.xml b/android/app/res/values-si/strings.xml
index 615227e66c27c6d8086fc377d2c996c1a7ff11dd..4da66455fbb0ee2ac3d3b1adbc2dc9b38002b506 100644
--- a/android/app/res/values-si/strings.xml
+++ b/android/app/res/values-si/strings.xml
@@ -128,4 +128,10 @@
"බ්ලූටූත් ශ්රව්යය"
"4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය"
"බ්ලූටූත් වෙත සබඳින්න"
+ "අහස්යානා ආකාරයේ බ්ලූටූත් ක්රියාත්මකයි"
+ "ඔබ බ්ලූටූත් ක්රියාත්මක කර තබා ගන්නේ නම්, ඔබ අහස්යානා ආකාරයේ සිටින මීළඟ වතාවේ එය ක්රියාත්මක කිරීමට ඔබේ දුරකථනයට මතක තිබෙනු ඇත."
+ "බ්ලූටූත් ක්රියාත්මකව පවතී"
+ "ඔබේ දුරකථනයට අහස්යානා ආකාරයේ බ්ලූටූත් ක්රියාත්මකව තබා ගැනීමට මතකයි. ඔබට බ්ලූටූත් ක්රියාත්මක වීමට අවශ්ය නොවේ නම් එය ක්රියාවිරහිත කරන්න."
+ "Wi-Fi සහ බ්ලූටූත් ක්රියාත්මකව පවතී"
+ "ඔබේ දුරකථනයට අහස්යානා ආකාරයේ Wi-Fi සහ බ්ලූටූත් ක්රියාත්මකව තබා ගැනීමට මතකයි. Wi-Fi සහ බ්ලූටූත් ඒවා ක්රියාත්මක වීමට ඔබට අවශ්ය නැතිනම් ක්රියා විරහිත කරන්න."
diff --git a/android/app/res/values-sk/strings.xml b/android/app/res/values-sk/strings.xml
index 1f427882b96094ec9215c42bf34f816aea77898a..fa4a7d450fa383e955951322ff42db85c48c9a85 100644
--- a/android/app/res/values-sk/strings.xml
+++ b/android/app/res/values-sk/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Súbory väčšie ako 4 GB sa nedajú preniesť"
"Pripojiť k zariadeniu Bluetooth"
+ "Rozhranie Bluetooth bude v režime v lietadle zapnuté"
+ "Ak ponecháte rozhranie Bluetooth zapnuté, váš telefón si zapamätá, že ho má ponechať zapnuté pri ďalšom aktivovaní režimu v lietadle"
+ "Rozhranie Bluetooth zostane zapnuté"
+ "Telefón si pamätá, aby v režime v lietadle nevypínal rozhranie Bluetooth. Ak ho nechcete ponechať zapnuté, vypnite ho."
+ "Wi‑Fi a Bluetooth zostanú zapnuté"
+ "Telefón si pamätá, aby v režime v lietadle nevypínal Wi‑Fi ani Bluetooth. Ak ich nechcete ponechať zapnuté, vypnite ich."
diff --git a/android/app/res/values-sl/strings.xml b/android/app/res/values-sl/strings.xml
index 845d3210e1b536939d9b9aac414ffb71bfc9b3fc..ac1de2bd28d9d33aae4cc7cac4b4c3c208b78836 100644
--- a/android/app/res/values-sl/strings.xml
+++ b/android/app/res/values-sl/strings.xml
@@ -128,4 +128,10 @@
"Zvok prek Bluetootha"
"Datotek, večjih od 4 GB, ni mogoče prenesti"
"Povezovanje z Bluetoothom"
+ "Bluetooth je vklopljen v načinu za letalo"
+ "Če pustite Bluetooth vklopljen, bo telefon ob naslednjem preklopu na način za letalo pustil Bluetooth vklopljen."
+ "Bluetooth ostane vklopljen"
+ "Telefon v načinu za letalo pusti Bluetooth vklopljen. Če ne želite, da ostane vklopljen, izklopite vmesnik Bluetooth."
+ "Wi-Fi in Bluetooth ostaneta vklopljena"
+ "Telefon v načinu za letalo pusti Wi-Fi in Bluetooth vklopljena. Če ne želite, da Wi-Fi in Bluetooth ostaneta vklopljena, ju izklopite."
diff --git a/android/app/res/values-sq/strings.xml b/android/app/res/values-sq/strings.xml
index 2d1a4b83a00e8e56884cbfb193eae2c6e2429c2d..e084c926d56fd828bf7fa8ca8424c51deff51b96 100644
--- a/android/app/res/values-sq/strings.xml
+++ b/android/app/res/values-sq/strings.xml
@@ -128,4 +128,10 @@
"Audioja e \"bluetooth-it\""
"Skedarët më të mëdhenj se 4 GB nuk mund të transferohen"
"Lidhu me Bluetooth"
+ "Bluetooth-i aktiv në modalitetin e aeroplanit"
+ "Nëse e mban Bluetooth-in të aktivizuar, telefoni yt do të kujtohet ta mbajë atë të aktivizuar herën tjetër kur të jesh në modalitetin e aeroplanit"
+ "Bluetooth qëndron i aktivizuar"
+ "Telefoni yt kujtohet që ta mbajë Bluetooth-in të aktivizuar në modalitetin e aeroplanit. Çaktivizo Bluetooth-in nëse nuk dëshiron që të qëndrojë i aktivizuar."
+ "Wi-Fi dhe Bluetooth-i qëndrojnë aktivë"
+ "Telefoni yt kujtohet që ta mbajë Wi-Fi dhe Bluetooth-in të aktivizuar në modalitetin e aeroplanit. Çaktivizo Wi-Fi dhe Bluetooth-in nëse nuk dëshiron që të qëndrojnë aktivë."
diff --git a/android/app/res/values-sr/strings.xml b/android/app/res/values-sr/strings.xml
index b9e05b3d24e0f32c05454fd6dca6d8ce90b5b720..b7092082851bc76716177ef1cd4b29dde1dfff6f 100644
--- a/android/app/res/values-sr/strings.xml
+++ b/android/app/res/values-sr/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth аудио"
"Не могу да се преносе датотеке веће од 4 GB"
"Повежи са Bluetooth-ом"
+ "Bluetooth је укључен у режиму рада у авиону"
+ "Ако одлучите да не искључујете Bluetooth, телефон ће запамтити да га не искључује следећи пут када будете у режиму рада у авиону"
+ "Bluetooth се не искључује"
+ "Телефон памти да не треба да искључује Bluetooth у режиму рада у авиону. Искључите Bluetooth ако не желите да остане укључен."
+ "WiFi и Bluetooth остају укључени"
+ "Телефон памти да не треба да искључује WiFi и Bluetooth у режиму рада у авиону. Искључите WiFi и Bluetooth ако не желите да остану укључени."
diff --git a/android/app/res/values-sv/strings.xml b/android/app/res/values-sv/strings.xml
index 5c11c0b77ace084466915c3f4e04b12af24ac01e..e8f951e8211d7419d395f9227c761bf69ff6794f 100644
--- a/android/app/res/values-sv/strings.xml
+++ b/android/app/res/values-sv/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth-ljud"
"Det går inte att överföra filer som är större än 4 GB"
"Anslut till Bluetooth"
+ "Håll Bluetooth aktiverat i flygplansläge"
+ "Om du håller wifi aktiverat kommer telefonen ihåg att hålla det aktiverat nästa gång du använder flygplansläge."
+ "Bluetooth förblir aktiverat"
+ "Telefonen kommer ihåg att hålla Bluetooth aktiverat i flygplansläge. Inaktivera Bluetooth om du inte vill att det ska hållas aktiverat."
+ "Wifi och Bluetooth ska vara aktiverade"
+ "Telefonen kommer ihåg att hålla wifi och Bluetooth aktiverade i flygplansläge. Du kan inaktivera wifi och Bluetooth om du inte vill hålla dem aktiverade."
diff --git a/android/app/res/values-sw/strings.xml b/android/app/res/values-sw/strings.xml
index 6b109a77f715a13c4b3d1d6c6a32cd2e1662062c..d3f5875b67c8f438a32d7d9daa8c53816eccfb4b 100644
--- a/android/app/res/values-sw/strings.xml
+++ b/android/app/res/values-sw/strings.xml
@@ -128,4 +128,10 @@
"Sauti ya Bluetooth"
"Haiwezi kutuma faili zinazozidi GB 4"
"Unganisha kwenye Bluetooth"
+ "Bluetooth itawashwa katika hali ya ndegeni"
+ "Usipozima Bluetooth, simu yako itakumbuka kuiwasha wakati mwingine unapokuwa katika hali ya ndegeni"
+ "Bluetooth itaendelea kuwaka"
+ "Simu yako itaendelea kuwasha Bluetooth katika hali ya ndegeni. Zima Bluetooth iwapo hutaki iendelee kuwaka."
+ "Wi-Fi na Bluetooth zitaendelea kuwaka"
+ "Simu yako itaendelea kuwasha Wi-Fi na Bluetooth ukiwa katika hali ya ndegeni. Zima Wi-Fi na Bluetooth ikiwa hutaki ziendelee kuwaka."
diff --git a/android/app/res/values-ta/strings.xml b/android/app/res/values-ta/strings.xml
index e46cb87475255ada2612cfbe47405388c44c0110..0cd15afd99934787a5b00323f6ae0cc81ee441fc 100644
--- a/android/app/res/values-ta/strings.xml
+++ b/android/app/res/values-ta/strings.xml
@@ -128,4 +128,10 @@
"புளூடூத் ஆடியோ"
"4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது"
"புளூடூத் உடன் இணை"
+ "விமானப் பயன்முறையில் புளூடூத்தை இயக்குதல்"
+ "புளூடூத்தை இயக்கத்தில் வைத்திருந்தால், அடுத்த முறை நீங்கள் விமானப் பயன்முறையைப் பயன்படுத்தும்போது உங்கள் மொபைல் புளூடூத்தை இயக்கத்தில் வைக்கும்"
+ "புளூடூத் இயக்கத்திலேயே இருக்கும்"
+ "விமானப் பயன்முறையில் புளூடூத்தை உங்கள் மொபைல் இயக்கத்திலேயே வைத்திருக்கும். புளூடூத்தை இயக்க விரும்பவில்லை என்றால் அதை முடக்கவும்."
+ "வைஃபையும் புளூடூத்தும் இயக்கத்திலேயே இருத்தல்"
+ "விமானப் பயன்முறையில் வைஃபையையும் புளூடூத்தையும் உங்கள் மொபைல் இயக்கத்திலேயே வைத்திருக்கும். வைஃபை மற்றும் புளூடூத்தை இயக்க விரும்பவில்லை என்றால் அவற்றை முடக்கவும்."
diff --git a/android/app/res/values-te/strings.xml b/android/app/res/values-te/strings.xml
index bdca35f5e85c5d86c4fc74a0514b93a4cce840b9..207ee3efa7dae9235e060fb05d0f6cac917d4113 100644
--- a/android/app/res/values-te/strings.xml
+++ b/android/app/res/values-te/strings.xml
@@ -28,8 +28,8 @@
"బ్లూటూత్ సేవలను ఉపయోగించడానికి, మీరు తప్పనిసరిగా ముందుగా బ్లూటూత్ను ప్రారంభించాలి."
"ఇప్పుడే బ్లూటూత్ను ప్రారంభించాలా?"
- "రద్దు చేయి"
- "ప్రారంభించు"
+ "రద్దు చేయండి"
+ "ప్రారంభించండి"
"ఫైల్ బదిలీ"
"ఇన్కమింగ్ ఫైల్ను ఆమోదించాలా?"
"తిరస్కరిస్తున్నాను"
@@ -48,14 +48,14 @@
"ఫైల్ బదిలీ"
"వీరి నుండి: \"%1$s\""
"ఫైల్: %1$s"
- "ఫైల్ పరిమాణం: %1$s"
+ "ఫైల్ సైజ్: %1$s"
"ఫైల్ను స్వీకరిస్తోంది…"
"ఆపివేయి"
"దాచు"
"దీని నుండి"
"ఫైల్ పేరు"
- "పరిమాణం"
+ "సైజ్"
"ఫైల్ స్వీకరించబడలేదు"
"ఫైల్: %1$s"
"కారణం: %1$s"
@@ -72,7 +72,7 @@
"మూసివేయి"
"సరే"
"తెలియని ఫైల్"
- "ఈ రకమైన ఫైల్ను నిర్వహించడానికి యాప్ ఏదీ లేదు. \n"
+ "ఈ రకమైన ఫైల్ను మేనేజ్ చేయడానికి యాప్ ఏదీ లేదు. \n"
"ఫైల్ లేదు"
"ఫైల్ ఉనికిలో లేదు. \n"
"దయచేసి వేచి ఉండండి..."
@@ -116,8 +116,8 @@
"లిస్ట్ నుండి క్లియర్ చేయండి"
"క్లియర్ చేయండి"
"ప్రస్తుతం ప్లే అవుతున్నవి"
- "సేవ్ చేయి"
- "రద్దు చేయి"
+ "సేవ్ చేయండి"
+ "రద్దు చేయండి"
"మీరు బ్లూటూత్ ద్వారా షేర్ చేయాలనుకునే ఖాతాలను ఎంచుకోండి. మీరు ఇప్పటికీ కనెక్ట్ చేస్తున్నప్పుడు ఖాతాలకు అందించే ఏ యాక్సెస్నైనా ఆమోదించాల్సి ఉంటుంది."
"మిగిలిన స్లాట్లు:"
"యాప్ చిహ్నం"
@@ -128,4 +128,10 @@
"బ్లూటూత్ ఆడియో"
"4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు"
"బ్లూటూత్కు కనెక్ట్ చేయి"
+ "విమానం మోడ్లో బ్లూటూత్ ఆన్ చేయబడింది"
+ "మీరు బ్లూటూత్ను ఆన్లో ఉంచినట్లయితే, మీరు తదుపరిసారి విమానం మోడ్లో ఉన్నప్పుడు దాన్ని ఆన్లో ఉంచాలని మీ ఫోన్ గుర్తుంచుకుంటుంది"
+ "బ్లూటూత్ ఆన్లో ఉంటుంది"
+ "విమానం మోడ్లో బ్లూటూత్ ఆన్లో ఉంచాలని మీ ఫోన్ గుర్తుంచుకుంటుంది. బ్లూటూత్ ఆన్లో ఉండకూడదనుకుంటే దాన్ని ఆఫ్ చేయండి."
+ "Wi-Fi, బ్లూటూత్ ఆన్లో ఉంటాయి"
+ "మీ ఫోన్ విమానం మోడ్లో Wi‑Fiని, బ్లూటూత్ని ఆన్లో ఉంచాలని గుర్తుంచుకుంటుంది. Wi-Fi, బ్లూటూత్ ఆన్లో ఉండకూడదనుకుంటే వాటిని ఆఫ్ చేయండి."
diff --git a/android/app/res/values-th/strings.xml b/android/app/res/values-th/strings.xml
index 5477f24f3ecdce5654c05ff05e7f67dfcc289ddb..a39afb5f83d4e9807e50390ed5d33e3a957911c0 100644
--- a/android/app/res/values-th/strings.xml
+++ b/android/app/res/values-th/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้"
"เชื่อมต่อบลูทูธ"
+ "บลูทูธเปิดอยู่ในโหมดบนเครื่องบิน"
+ "หากเปิดบลูทูธไว้ โทรศัพท์จะจำว่าต้องเปิดบลูทูธในครั้งถัดไปที่คุณอยู่ในโหมดบนเครื่องบิน"
+ "บลูทูธเปิดอยู่"
+ "โทรศัพท์จำว่าจะต้องเปิดบลูทูธไว้ในโหมดบนเครื่องบิน ปิดบลูทูธหากคุณไม่ต้องการให้เปิดไว้"
+ "Wi-Fi และบลูทูธยังเปิดอยู่"
+ "โทรศัพท์จำว่าจะต้องเปิด Wi-Fi และบลูทูธไว้ในโหมดบนเครื่องบิน ปิด Wi-Fi และบลูทูธหากคุณไม่ต้องการให้เปิดไว้"
diff --git a/android/app/res/values-tl/strings.xml b/android/app/res/values-tl/strings.xml
index d4c7e1e0697eddd9aafb1e8ab101b111732c2028..b84dc9aa4de169db9be5295d7f9967ad22338335 100644
--- a/android/app/res/values-tl/strings.xml
+++ b/android/app/res/values-tl/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Hindi maililipat ang mga file na mas malaki sa 4GB"
"Kumonekta sa Bluetooth"
+ "Naka-on ang Bluetooth sa airplane mode"
+ "Kung papanatilihin mong naka-on ang Bluetooth, tatandaan ng iyong telepono na panatilihin itong naka-on sa susunod na nasa airplane mode ka"
+ "Mananatiling naka-on ang Bluetooth"
+ "Tinatandaan ng iyong telepono na panatilihing naka-on ang Bluetooth habang nasa airplane mode. I-off ang Bluetooth kung ayaw mo itong manatiling naka-on."
+ "Mananatiling naka-on ang Wi-Fi at Bluetooth"
+ "Tinatandaan ng iyong telepono na panatilihing naka-on ang Wi-Fi at Bluetooth habang nasa airplane mode. I-off ang Wi-Fi at Bluetooth kung ayaw mong manatiling naka-on ang mga ito."
diff --git a/android/app/res/values-tr/strings.xml b/android/app/res/values-tr/strings.xml
index f622f2f015b245ef2c5f128c2f2abb28a2156761..c3c65759d5b2407f3d3b80f1390c5f508e49fae4 100644
--- a/android/app/res/values-tr/strings.xml
+++ b/android/app/res/values-tr/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Ses"
"4 GB\'tan büyük dosyalar aktarılamaz"
"Bluetooth\'a bağlan"
+ "Uçak modundayken Bluetooth açık"
+ "Kablosuz bağlantıyı açık tutarsanız telefonunuz, daha sonra tekrar uçak modunda olduğunuzda kablosuz bağlantıyı açık tutar"
+ "Bluetooth açık kalır"
+ "Telefonunuz, uçak modundayken Bluetooth\'u açık tutmayı hatırlar. Açık kalmasını istemiyorsanız Bluetooth\'u kapatın."
+ "Kablosuz bağlantı ve Bluetooth açık kalır"
+ "Telefonunuz, uçak modundayken kablosuz bağlantıyı ve Bluetooth\'u açık tutmayı hatırlar. Açık kalmasını istemiyorsanız kablosuz bağlantıyı ve Bluetooth\'u kapatın."
diff --git a/android/app/res/values-uk/strings.xml b/android/app/res/values-uk/strings.xml
index b00400ea386386f9b71bc7153aacd473144f53c2..4290e6a9b42035c46cf9c769d9f7276ec28f7b71 100644
--- a/android/app/res/values-uk/strings.xml
+++ b/android/app/res/values-uk/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth Audio"
"Не можна перенести файли, більші за 4 ГБ"
"Підключитися до Bluetooth"
+ "Bluetooth увімкнено в режимі польоту"
+ "Якщо ви не вимкнете Bluetooth на телефоні, ця функція залишатиметься ввімкненою під час наступного використання режиму польоту"
+ "Bluetooth не буде вимкнено"
+ "У режимі польоту функція Bluetooth на телефоні залишатиметься ввімкненою. За бажання її можна вимкнути."
+ "Wi-Fi і Bluetooth залишаються ввімкненими"
+ "У режимі польоту функції Wi-Fi і Bluetooth на телефоні залишатимуться ввімкненими. За бажання їх можна вимкнути."
diff --git a/android/app/res/values-ur/strings.xml b/android/app/res/values-ur/strings.xml
index 0cfee4c29cdc08a02064ee4867b482dee3ad7c96..fdbffdc055ae25e288ce410c72364ebdaefcf60b 100644
--- a/android/app/res/values-ur/strings.xml
+++ b/android/app/res/values-ur/strings.xml
@@ -128,4 +128,10 @@
"بلوٹوتھ آڈیو"
"4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں"
"بلوٹوتھ سے منسلک کریں"
+ "ہوائی جہاز وضع میں بلوٹوتھ آن ہے"
+ "اگر آپ بلوٹوتھ کو آن رکھتے ہیں تو آپ کا فون آپ کے اگلی مرتبہ ہوائی جہاز وضع میں ہونے پر اسے آن رکھنا یاد رکھے گا"
+ "بلوٹوتھ آن رہتا ہے"
+ "آپ کا فون ہوائی جہاز وضع میں بلوٹوتھ کو آن رکھنا یاد رکھتا ہے۔ اگر آپ نہیں چاہتے ہیں کہ بلوٹوتھ آن رہے تو اسے آف کریں۔"
+ "Wi-Fi اور بلوٹوتھ آن رہنے دیں"
+ "آپ کا فون ہوائی جہاز وضع میں Wi-Fi اور بلوٹوتھ کو آن رکھنا یاد رکھتا ہے۔ اگر آپ نہیں چاہتے ہیں کہ Wi-Fi اور بلوٹوتھ آن رہیں تو انہیں آف کریں۔"
diff --git a/android/app/res/values-uz/strings.xml b/android/app/res/values-uz/strings.xml
index a4aa29bd95456bfce6c2809da37023489bfdde5a..8b857fafcf157cde3e27bb88e649ca8e286a4eaa 100644
--- a/android/app/res/values-uz/strings.xml
+++ b/android/app/res/values-uz/strings.xml
@@ -128,4 +128,10 @@
"Bluetooth orqali ovoz"
"4 GBdan katta hajmli videolar o‘tkazilmaydi"
"Bluetoothga ulanish"
+ "Bluetooth parvoz rejimida yoniq"
+ "Bluetooth yoniq qolsa, telefon keyingi safar parvoz rejimida ham uni yoniq qoldiradi."
+ "Bluetooth yoniq turadi"
+ "Telefoningiz parvoz rejimida Bluetooth yoqilganini eslab qoladi. Yoniq qolmasligi uchun Bluetooth aloqasini oʻchiring."
+ "Wi-Fi va Bluetooth yoniq qoladi"
+ "Telefoningiz parvoz rejimida Wi‑Fi va Bluetooth yoqilganini eslab qoladi. Yoniq qolmasligi uchun Wi-Fi va Bluetooth aloqasini oʻchiring."
diff --git a/android/app/res/values-vi/strings.xml b/android/app/res/values-vi/strings.xml
index 53de8c750eaf50e3d5895e8fc8ae00d084af6d7a..f061077d9a4147c7f0f50990bbb389edb2181a26 100644
--- a/android/app/res/values-vi/strings.xml
+++ b/android/app/res/values-vi/strings.xml
@@ -128,4 +128,10 @@
"Âm thanh Bluetooth"
"Không thể chuyển những tệp lớn hơn 4 GB"
"Kết nối với Bluetooth"
+ "Bluetooth đang bật ở chế độ trên máy bay"
+ "Nếu bạn không tắt Bluetooth, điện thoại sẽ luôn bật Bluetooth vào lần tiếp theo bạn dùng chế độ trên máy bay"
+ "Bluetooth luôn bật"
+ "Điện thoại của bạn sẽ luôn bật Bluetooth ở chế độ trên máy bay. Nếu không muốn như vậy thì bạn có thể tắt Bluetooth."
+ "Wi-Fi và Bluetooth vẫn đang bật"
+ "Điện thoại của bạn sẽ luôn bật Wi-Fi và Bluetooth ở chế độ trên máy bay. Tắt Wi-Fi và Bluetooth nếu bạn không muốn tiếp tục bật."
diff --git a/android/app/res/values-zh-rCN/strings.xml b/android/app/res/values-zh-rCN/strings.xml
index 4133a314bad198cc1fe262d9be74a0a547bcfcdc..0eda93813e4605197db8488e1f601e39ac435ffd 100644
--- a/android/app/res/values-zh-rCN/strings.xml
+++ b/android/app/res/values-zh-rCN/strings.xml
@@ -109,8 +109,8 @@
"所有内容都将从列表中清除。"
"蓝牙共享:已发送文件"
"蓝牙共享:已接收文件"
- "{count,plural, =1{# 个文件发送失败。}other{# 个文件发送失败。}}"
- "{count,plural, =1{# 个文件发送成功,%1$s}other{# 个文件发送成功,%1$s}}"
+ "{count,plural, =1{# 个文件传输失败。}other{# 个文件传输失败。}}"
+ "{count,plural, =1{# 个文件传输成功,%1$s}other{# 个文件传输成功,%1$s}}"
"清除列表"
"打开"
"从列表中清除"
@@ -128,4 +128,10 @@
"蓝牙音频"
"无法传输 4GB 以上的文件"
"连接到蓝牙"
+ "在飞行模式下蓝牙保持开启状态"
+ "如果您不关闭蓝牙,那么您下次进入飞行模式时手机将记住保持开启蓝牙"
+ "蓝牙保持开启状态"
+ "在飞行模式下手机将记住保持开启蓝牙。如果您不想保持开启和蓝牙,请关闭蓝牙。"
+ "WLAN 和蓝牙保持开启状态"
+ "在飞行模式下手机将记住保持开启 WLAN 和蓝牙。如果您不想保持开启 WLAN 和蓝牙,请关闭 WLAN 和蓝牙。"
diff --git a/android/app/res/values-zh-rHK/strings.xml b/android/app/res/values-zh-rHK/strings.xml
index cd33c81204dc6b1aac431fe011f72a459551e05f..cce5f3efd389caec2da743246b31775978a12334 100644
--- a/android/app/res/values-zh-rHK/strings.xml
+++ b/android/app/res/values-zh-rHK/strings.xml
@@ -128,4 +128,10 @@
"藍牙音訊"
"無法轉移 4 GB 以上的檔案"
"連接藍牙"
+ "在飛航模式中保持藍牙開啟"
+ "如果您不關閉藍牙,下次手機進入飛行模式時,藍牙將保持開啟"
+ "保持藍牙連線"
+ "手機會記得在飛行模式下保持藍牙開啟。如果您不希望保持開啟,請關閉藍牙。"
+ "Wi-Fi 和藍牙保持開啟"
+ "手機會記得在飛行模式下保持 Wi-Fi 及藍牙開啟。如果您不希望保持開啟,請關閉 Wi-Fi 及藍牙。"
diff --git a/android/app/res/values-zh-rTW/strings.xml b/android/app/res/values-zh-rTW/strings.xml
index a5c5b8e12a64654f683171dfbce131ef098b36c7..cbaadb23825d033a15fd5c2c94b49db8013f932d 100644
--- a/android/app/res/values-zh-rTW/strings.xml
+++ b/android/app/res/values-zh-rTW/strings.xml
@@ -128,4 +128,10 @@
"藍牙音訊"
"無法轉移大於 4GB 的檔案"
"使用藍牙連線"
+ "在飛航模式下保持藍牙開啟狀態"
+ "如果不關閉藍牙,下次手機進入飛航模式時,藍牙將保持開啟"
+ "藍牙會保持開啟狀態"
+ "手機會記得在飛航模式下保持藍牙開啟。如果不要保持開啟,請關閉藍牙。"
+ "Wi-Fi 和藍牙會保持開啟狀態"
+ "手機會記得在飛航模式下保持 Wi-Fi 和藍牙開啟。如果不要保持開啟,請關閉 Wi-Fi 和藍牙。"
diff --git a/android/app/res/values-zu/strings.xml b/android/app/res/values-zu/strings.xml
index 18c798f38c037eb792819ec46bb73d8ebd5468d0..c4c03cd67f0f8e603e6886b8b79a5fafc2042b20 100644
--- a/android/app/res/values-zu/strings.xml
+++ b/android/app/res/values-zu/strings.xml
@@ -128,4 +128,10 @@
"Umsindo we-Bluetooth"
"Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa"
"Xhumeka ku-Bluetooth"
+ "I-Bluetooth ivuliwe kumodi yendiza"
+ "Uma ugcina i-Wi‑Fi ivuliwe, ifoni yakho izokhumbula ukuyigcina ivuliwe ngesikhathi esilandelayo uma ukumodi yendiza."
+ "I-Bluetooth ihlala ivuliwe"
+ "Ifoni yakho ikhumbula ukugcina i-Bluetooth ivuliwe kumodi yendiza. Vala i-Bluetooth uma ungafuni ukuthi ihlale ivuliwe."
+ "I-Wi-Fi ne-Bluetooth kuhlala kuvuliwe"
+ "Ifoni yakho ikhumbula ukugcina i-Wi-Fi ne-Bluetooth kuvuliwe kumodi yendiza. Vala i-Wi-Fi ne-Bluetooth uma ungafuni ukuthi ihlale ivuliwe."
diff --git a/android/app/res/values/config.xml b/android/app/res/values/config.xml
index 8e8c755b2b5e74e418735b4a332dfa73c6c0eb6a..913c77f98ac5c35f4f807894415076765c2c42ac 100644
--- a/android/app/res/values/config.xml
+++ b/android/app/res/values/config.xml
@@ -62,11 +62,8 @@
false
-
- false
-
- false
+ true
false
@@ -85,6 +82,7 @@
4001
5001
6001
+ 7001
true
diff --git a/android/app/res/values/strings.xml b/android/app/res/values/strings.xml
index cc489220baa81945fc52af49dfe41948da58e96c..46b9ad4d3a11c16ee73551fb34fbedb242668d5e 100644
--- a/android/app/res/values/strings.xml
+++ b/android/app/res/values/strings.xml
@@ -248,4 +248,10 @@
Bluetooth Audio
Files bigger than 4GB cannot be transferred
Connect to Bluetooth
+ Bluetooth on in airplane mode
+ If you keep Bluetooth on, your phone will remember to keep it on the next time you\'re in airplane mode
+ Bluetooth stays on
+ Your phone remembers to keep Bluetooth on in airplane mode. Turn off Bluetooth if you don\'t want it to stay on.
+ Wi-Fi and Bluetooth stay on
+ Your phone remembers to keep Wi-Fi and Bluetooth on in airplane mode. Turn off Wi-Fi and Bluetooth if you don\'t want them to stay on.
diff --git a/android/app/src/com/android/bluetooth/AlertActivity.java b/android/app/src/com/android/bluetooth/AlertActivity.java
index 858317350473aad4bb94e3c6630715d0fb1e1763..cb5e15eff3028342c8e76904b372ec42e69cf0d8 100644
--- a/android/app/src/com/android/bluetooth/AlertActivity.java
+++ b/android/app/src/com/android/bluetooth/AlertActivity.java
@@ -27,6 +27,9 @@ import android.os.Bundle;
import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+
+import com.android.internal.annotations.VisibleForTesting;
/**
* An activity that follows the visual style of an AlertDialog.
@@ -119,6 +122,11 @@ public abstract class AlertActivity extends Activity implements DialogInterface.
mAlert.getButton(identifier).setEnabled(enable);
}
+ @VisibleForTesting
+ public Button getButton(int identifier) {
+ return mAlert.getButton(identifier);
+ }
+
@Override
protected void onDestroy() {
if (mAlert != null) {
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1ea7d4e952c4c01cd2cb635132505bdbaa387cd
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2022 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;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.PeriodicAdvertisingCallback;
+import android.bluetooth.le.PeriodicAdvertisingManager;
+import android.bluetooth.le.ScanResult;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony;
+import android.util.Log;
+
+import com.android.bluetooth.gatt.AppAdvertiseStats;
+import com.android.bluetooth.gatt.ContextMap;
+import com.android.bluetooth.gatt.GattService;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.obex.HeaderSet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Proxy class for method calls to help with unit testing
+ */
+public class BluetoothMethodProxy {
+ private static final String TAG = BluetoothMethodProxy.class.getSimpleName();
+ private static final Object INSTANCE_LOCK = new Object();
+ private static BluetoothMethodProxy sInstance;
+
+ private BluetoothMethodProxy() {
+ }
+
+ /**
+ * Get the singleton instance of proxy
+ *
+ * @return the singleton instance, guaranteed not null
+ */
+ public static BluetoothMethodProxy getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sInstance == null) {
+ sInstance = new BluetoothMethodProxy();
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Allow unit tests to substitute BluetoothPbapMethodCallProxy with a test instance
+ *
+ * @param proxy a test instance of the BluetoothPbapMethodCallProxy
+ */
+ @VisibleForTesting
+ public static void setInstanceForTesting(BluetoothMethodProxy proxy) {
+ Utils.enforceInstrumentationTestMode();
+ synchronized (INSTANCE_LOCK) {
+ Log.d(TAG, "setInstanceForTesting(), set to " + proxy);
+ sInstance = proxy;
+ }
+ }
+
+ /**
+ * Proxies {@link ContentResolver#query(Uri, String[], String, String[], String)}.
+ */
+ public Cursor contentResolverQuery(ContentResolver contentResolver, final Uri contentUri,
+ final String[] projection, final String selection, final String[] selectionArgs,
+ final String sortOrder) {
+ return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
+ */
+ public Cursor contentResolverQuery(ContentResolver contentResolver, final Uri contentUri,
+ final String[] projection, final Bundle queryArgs,
+ final CancellationSignal cancellationSignal) {
+ return contentResolver.query(contentUri, projection, queryArgs, cancellationSignal);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#insert(Uri, ContentValues)}.
+ */
+ public Uri contentResolverInsert(ContentResolver contentResolver, final Uri contentUri,
+ final ContentValues contentValues) {
+ return contentResolver.insert(contentUri, contentValues);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#update(Uri, ContentValues, String, String[])}.
+ */
+ public int contentResolverUpdate(ContentResolver contentResolver, final Uri contentUri,
+ final ContentValues contentValues, String where, String[] selectionArgs) {
+ return contentResolver.update(contentUri, contentValues, where, selectionArgs);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#delete(Uri, String, String[])}.
+ */
+ public int contentResolverDelete(ContentResolver contentResolver, final Uri url,
+ final String where,
+ final String[] selectionArgs) {
+ return contentResolver.delete(url, where, selectionArgs);
+ }
+
+ /**
+ * Proxies {@link BluetoothAdapter#isEnabled()}.
+ */
+ public boolean bluetoothAdapterIsEnabled(BluetoothAdapter adapter) {
+ return adapter.isEnabled();
+ }
+
+ /**
+ * Proxies {@link ContentResolver#openFileDescriptor(Uri, String)}.
+ */
+ public ParcelFileDescriptor contentResolverOpenFileDescriptor(ContentResolver contentResolver,
+ final Uri uri, final String mode) throws FileNotFoundException {
+ return contentResolver.openFileDescriptor(uri, mode);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
+ */
+ public AssetFileDescriptor contentResolverOpenAssetFileDescriptor(
+ ContentResolver contentResolver, final Uri uri, final String mode)
+ throws FileNotFoundException {
+ return contentResolver.openAssetFileDescriptor(uri, mode);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#openInputStream(Uri)}.
+ */
+ public InputStream contentResolverOpenInputStream(ContentResolver contentResolver,
+ final Uri uri) throws FileNotFoundException {
+ return contentResolver.openInputStream(uri);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#acquireUnstableContentProviderClient(String)}.
+ */
+ public ContentProviderClient contentResolverAcquireUnstableContentProviderClient(
+ ContentResolver contentResolver, @NonNull String name) {
+ return contentResolver.acquireUnstableContentProviderClient(name);
+ }
+
+ /**
+ * Proxies {@link Context#sendBroadcast(Intent)}.
+ */
+ public void contextSendBroadcast(Context context, @RequiresPermission Intent intent) {
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Proxies {@link HeaderSet#getHeader}.
+ */
+ public Object getHeader(HeaderSet headerSet, int headerId) throws IOException {
+ return headerSet.getHeader(headerId);
+ }
+
+ /**
+ * Proxies {@link Context#getSystemService(Class)}.
+ */
+ public T getSystemService(Context context, Class serviceClass) {
+ return context.getSystemService(serviceClass);
+ }
+
+ /**
+ * Proxies {@link Telephony.Threads#getOrCreateThreadId(Context, Set )}.
+ */
+ public long telephonyGetOrCreateThreadId(Context context, Set recipients) {
+ return Telephony.Threads.getOrCreateThreadId(context, recipients);
+ }
+
+ /**
+ * Proxies {@link PeriodicAdvertisingManager#registerSync(ScanResult, int, int,
+ * PeriodicAdvertisingCallback, Handler)}.
+ */
+ public void periodicAdvertisingManagerRegisterSync(PeriodicAdvertisingManager manager,
+ ScanResult scanResult, int skip, int timeout,
+ PeriodicAdvertisingCallback callback, Handler handler) {
+ manager.registerSync(scanResult, skip, timeout, callback, handler);
+ }
+
+ /**
+ * Proxies {@link PeriodicAdvertisingManager#transferSync}.
+ */
+ public void periodicAdvertisingManagerTransferSync(PeriodicAdvertisingManager manager,
+ BluetoothDevice bda, int serviceData, int syncHandle) {
+ manager.transferSync(bda, serviceData, syncHandle);
+ }
+
+ /**
+ * Proxies {@link PeriodicAdvertisingManager#transferSetInfo}.
+ */
+ public void periodicAdvertisingManagerTransferSetInfo(
+ PeriodicAdvertisingManager manager, BluetoothDevice bda, int serviceData,
+ int advHandle, PeriodicAdvertisingCallback callback) {
+ manager.transferSetInfo(bda, serviceData, advHandle, callback);
+ }
+
+ /**
+ * Proxies {@link AppAdvertiseStats}.
+ */
+ public AppAdvertiseStats createAppAdvertiseStats(int appUid, int id, String name,
+ ContextMap map, GattService service) {
+ return new AppAdvertiseStats(appUid, id, name, map, service);
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/mapclient/obex/ObexAppParameters.java b/android/app/src/com/android/bluetooth/ObexAppParameters.java
similarity index 99%
rename from android/app/src/com/android/bluetooth/mapclient/obex/ObexAppParameters.java
rename to android/app/src/com/android/bluetooth/ObexAppParameters.java
index 28e8b1c8f58fda06d4e5e47104ef878acc6ec792..80a3645cda603490c4967c91d73c6e0b9d0e05f6 100644
--- a/android/app/src/com/android/bluetooth/mapclient/obex/ObexAppParameters.java
+++ b/android/app/src/com/android/bluetooth/ObexAppParameters.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.bluetooth.mapclient;
+package com.android.bluetooth;
import com.android.obex.HeaderSet;
diff --git a/android/app/src/com/android/bluetooth/Utils.java b/android/app/src/com/android/bluetooth/Utils.java
index abe63bafe5944cd16c0c45d0f682460676ee9d3a..b22b3aa03384a13a604fce1602454f495a60eef9 100644
--- a/android/app/src/com/android/bluetooth/Utils.java
+++ b/android/app/src/com/android/bluetooth/Utils.java
@@ -373,9 +373,12 @@ public final class Utils {
*/
public static boolean isPackageNameAccurate(Context context, String callingPackage,
int callingUid) {
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+
// Verifies the integrity of the calling package name
try {
- int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0);
+ int packageUid = context.createContextAsUser(callingUser, 0)
+ .getPackageManager().getPackageUid(callingPackage, 0);
if (packageUid != callingUid) {
Log.e(TAG, "isPackageNameAccurate: App with package name " + callingPackage
+ " is UID " + packageUid + " but caller is " + callingUid);
@@ -424,8 +427,6 @@ public final class Utils {
"Need DUMP permission");
}
- /**
- */
public static AttributionSource getCallingAttributionSource(Context context) {
int callingUid = Binder.getCallingUid();
if (callingUid == android.os.Process.ROOT_UID) {
@@ -460,6 +461,9 @@ public final class Utils {
@SuppressLint("AndroidFrameworkRequiresPermission")
private static boolean checkPermissionForDataDelivery(Context context, String permission,
AttributionSource attributionSource, String message) {
+ if (isInstrumentationTestMode()) {
+ return true;
+ }
// STOPSHIP(b/188391719): enable this security enforcement
// attributionSource.enforceCallingUid();
AttributionSource currentAttribution = new AttributionSource
@@ -617,7 +621,7 @@ public final class Utils {
return false;
}
- public static boolean checkCallerIsSystemOrActiveUser() {
+ private static boolean checkCallerIsSystemOrActiveUser() {
int callingUid = Binder.getCallingUid();
UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
@@ -638,7 +642,7 @@ public final class Utils {
return checkCallerIsSystemOrActiveUser(tag + "." + method + "()");
}
- public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) {
+ private static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) {
if (context == null) {
return checkCallerIsSystemOrActiveUser();
}
@@ -666,6 +670,9 @@ public final class Utils {
}
public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context, String tag) {
+ if (isInstrumentationTestMode()) {
+ return true;
+ }
final boolean res = checkCallerIsSystemOrActiveOrManagedUser(context);
if (!res) {
Log.w(TAG, tag + " - Not allowed for"
@@ -1001,7 +1008,8 @@ public final class Utils {
}
values.put(Telephony.Sms.ERROR_CODE, 0);
- return 1 == context.getContentResolver().update(uri, values, null, null);
+ return 1 == BluetoothMethodProxy.getInstance().contentResolverUpdate(
+ context.getContentResolver(), uri, values, null, null);
}
/**
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 3084aaa3b9a063f3a07239c0a3e4b02f85c2a505..0b1c16b2ccdb3659c48710dfaba6d356b8ac73c5 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -37,6 +37,9 @@ class A2dpCodecConfig {
private static final boolean DBG = true;
private static final String TAG = "A2dpCodecConfig";
+ // TODO(b/240635097): remove in U
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6;
+
private Context mContext;
private A2dpNativeInterface mA2dpNativeInterface;
@@ -53,6 +56,8 @@ class A2dpCodecConfig {
BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private @CodecPriority int mA2dpSourceCodecPriorityLc3 =
BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPriorityOpus =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private BluetoothCodecConfig[] mCodecConfigOffloading = new BluetoothCodecConfig[0];
@@ -243,6 +248,16 @@ class A2dpCodecConfig {
mA2dpSourceCodecPriorityLc3 = value;
}
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_opus);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ mA2dpSourceCodecPriorityOpus = value;
+ }
+
BluetoothCodecConfig codecConfig;
BluetoothCodecConfig[] codecConfigArray =
new BluetoothCodecConfig[6];
@@ -272,8 +287,9 @@ class A2dpCodecConfig {
.build();
codecConfigArray[4] = codecConfig;
codecConfig = new BluetoothCodecConfig.Builder()
- .setCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3)
- .setCodecPriority(mA2dpSourceCodecPriorityLc3)
+ // TODO(b/240635097): update in U
+ .setCodecType(SOURCE_CODEC_TYPE_OPUS)
+ .setCodecPriority(mA2dpSourceCodecPriorityOpus)
.build();
codecConfigArray[5] = codecConfig;
@@ -282,14 +298,16 @@ class A2dpCodecConfig {
public void switchCodecByBufferSize(
BluetoothDevice device, boolean isLowLatency, int currentCodecType) {
- if ((isLowLatency && currentCodecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3)
- || (!isLowLatency && currentCodecType != BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3)) {
+ // TODO(b/240635097): update in U
+ if ((isLowLatency && currentCodecType == SOURCE_CODEC_TYPE_OPUS)
+ || (!isLowLatency && currentCodecType != SOURCE_CODEC_TYPE_OPUS)) {
return;
}
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
for (int i = 0; i < codecConfigArray.length; i++){
BluetoothCodecConfig codecConfig = codecConfigArray[i];
- if (codecConfig.getCodecType() == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) {
+ // TODO(b/240635097): update in U
+ if (codecConfig.getCodecType() == SOURCE_CODEC_TYPE_OPUS) {
if (isLowLatency) {
codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
} else {
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
index c152741fc96ad765d6a301fde0ebcc60297bd56f..9bc4fad2b933c47038befb0fb24d707cf683697c 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -71,6 +71,9 @@ public class A2dpService extends ProfileService {
private static final boolean DBG = true;
private static final String TAG = "A2dpService";
+ // TODO(b/240635097): remove in U
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6;
+
private static A2dpService sA2dpService;
private AdapterService mAdapterService;
@@ -490,6 +493,20 @@ public class A2dpService extends ProfileService {
if (mActiveDevice == null) return;
previousActiveDevice = mActiveDevice;
}
+
+ int prevActiveConnectionState = getConnectionState(previousActiveDevice);
+
+ // As per b/202602952, if we remove the active device due to a disconnection,
+ // we need to check if another device is connected and set it active instead.
+ // Calling this before any other active related calls has the same effect as
+ // a classic active device switch.
+ BluetoothDevice fallbackdevice = getFallbackDevice();
+ if (fallbackdevice != null && prevActiveConnectionState
+ != BluetoothProfile.STATE_CONNECTED) {
+ setActiveDevice(fallbackdevice);
+ return;
+ }
+
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
updateAndBroadcastActiveDevice(null);
@@ -499,7 +516,7 @@ public class A2dpService extends ProfileService {
// device, the user has explicitly switched the output to the local device and music
// should continue playing. Otherwise, the remote device has been indeed disconnected
// and audio should be suspended before switching the output to the local device.
- boolean stopAudio = forceStopPlayingAudio || (getConnectionState(previousActiveDevice)
+ boolean stopAudio = forceStopPlayingAudio || (prevActiveConnectionState
!= BluetoothProfile.STATE_CONNECTED);
mAudioManager.handleBluetoothActiveDeviceChanged(null, previousActiveDevice,
BluetoothProfileConnectionInfo.createA2dpInfo(!stopAudio, -1));
@@ -586,6 +603,7 @@ public class A2dpService extends ProfileService {
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
updateAndBroadcastActiveDevice(device);
+ updateLowLatencyAudioSupport(device);
BluetoothDevice newActiveDevice = null;
synchronized (mStateMachines) {
@@ -805,6 +823,7 @@ public class A2dpService extends ProfileService {
Log.e(TAG, "enableOptionalCodecs: Codec status is null");
return;
}
+ updateLowLatencyAudioSupport(device);
mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
}
@@ -835,6 +854,7 @@ public class A2dpService extends ProfileService {
Log.e(TAG, "disableOptionalCodecs: Codec status is null");
return;
}
+ updateLowLatencyAudioSupport(device);
mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
}
@@ -1194,6 +1214,34 @@ public class A2dpService extends ProfileService {
}
}
+ /**
+ * Check for low-latency codec support and inform AdapterService
+ *
+ * @param device device whose audio low latency will be allowed or disallowed
+ */
+ @VisibleForTesting
+ public void updateLowLatencyAudioSupport(BluetoothDevice device) {
+ synchronized (mStateMachines) {
+ A2dpStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ return;
+ }
+ BluetoothCodecStatus codecStatus = sm.getCodecStatus();
+ boolean lowLatencyAudioAllow = false;
+ BluetoothCodecConfig lowLatencyCodec = new BluetoothCodecConfig.Builder()
+ .setCodecType(SOURCE_CODEC_TYPE_OPUS) // remove in U
+ .build();
+
+ if (codecStatus != null
+ && codecStatus.isCodecConfigSelectable(lowLatencyCodec)
+ && getOptionalCodecsEnabled(device)
+ == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+ lowLatencyAudioAllow = true;
+ }
+ mAdapterService.allowLowLatencyAudio(lowLatencyAudioAllow, device);
+ }
+ }
+
private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
if ((device == null) || (fromState == toState)) {
return;
@@ -1241,6 +1289,16 @@ public class A2dpService extends ProfileService {
}
}
+ /**
+ * Retrieves the most recently connected device in the A2DP connected devices list.
+ */
+ public BluetoothDevice getFallbackDevice() {
+ DatabaseManager dbManager = mAdapterService.getDatabase();
+ return dbManager != null ? dbManager
+ .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
+ : null;
+ }
+
/**
* Binder object: must be a static class or memory leak may occur.
*/
@@ -1251,8 +1309,11 @@ public class A2dpService extends ProfileService {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private A2dpService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
}
@@ -1641,6 +1702,9 @@ public class A2dpService extends ProfileService {
}
public void switchCodecByBufferSize(BluetoothDevice device, boolean isLowLatency) {
+ if (getOptionalCodecsEnabled(device) != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+ return;
+ }
mA2dpCodecConfig.switchCodecByBufferSize(
device, isLowLatency, getCodecStatus(device).getCodecConfig().getCodecType());
}
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index f14ccac6f4b7e89abf6303df59fb8c78d01685b6..9e7323fd0fb2de523b818a6b3ac88924523cd3ab 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -76,6 +76,9 @@ final class A2dpStateMachine extends StateMachine {
private static final boolean DBG = true;
private static final String TAG = "A2dpStateMachine";
+ // TODO(b/240635097): remove in U
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6;
+
static final int CONNECT = 1;
static final int DISCONNECT = 2;
@VisibleForTesting
@@ -480,6 +483,7 @@ final class A2dpStateMachine extends StateMachine {
// codecs (perhaps it's had a firmware update, etc.) and save that state if
// it differs from what we had saved before.
mA2dpService.updateOptionalCodecsSupport(mDevice);
+ mA2dpService.updateLowLatencyAudioSupport(mDevice);
broadcastConnectionState(mConnectionState, mLastConnectionState);
// Upon connected, the audio starts out as stopped
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -652,6 +656,7 @@ final class A2dpStateMachine extends StateMachine {
// for this codec change event.
mA2dpService.updateOptionalCodecsSupport(mDevice);
}
+ mA2dpService.updateLowLatencyAudioSupport(mDevice);
if (mA2dpOffloadEnabled) {
boolean update = false;
BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -666,6 +671,13 @@ final class A2dpStateMachine extends StateMachine {
&& (prevCodecConfig.getCodecSpecific1()
!= newCodecConfig.getCodecSpecific1())) {
update = true;
+ } else if ((newCodecConfig.getCodecType()
+ == SOURCE_CODEC_TYPE_OPUS) // TODO(b/240635097): update in U
+ && (prevCodecConfig != null)
+ // check framesize field
+ && (prevCodecConfig.getCodecSpecific1()
+ != newCodecConfig.getCodecSpecific1())) {
+ update = true;
}
if (update) {
mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
diff --git a/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 52cbd2176630b4d06509739e08b858efb47f3598..5c945d78064b2052b169802381704cfd38f48a61 100644
--- a/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -190,14 +190,18 @@ public class A2dpSinkService extends ProfileService {
}
//Binder object: Must be static class or memory leak may occur
- private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
+ @VisibleForTesting
+ static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
implements IProfileServiceBinder {
private A2dpSinkService mService;
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private A2dpSinkService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
}
diff --git a/android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index c440d863d5044232bce32b08d050c003acd5cfe7..47f325c839559103d1dec38a0266a453007d23ca 100644
--- a/android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -509,11 +509,8 @@ public class AvrcpTargetService extends ProfileService {
@Override
public void sendVolumeChanged(int volume) {
- if (!Utils.callerIsSystemOrActiveUser(TAG, "sendVolumeChanged")) {
- return;
- }
-
- if (mService == null) {
+ if (mService == null
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
return;
}
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java
index 4f943bbca12e57ad7f4006ad4ecb350621ace4bc..3f46b4419e411b7d73dbd1c7adf4e0a5be91ed72 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java
@@ -26,6 +26,7 @@ import android.os.Message;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.ResponseCodes;
@@ -229,7 +230,8 @@ public class AvrcpBipClient {
/**
* Update our client's connection state and notify of the new status
*/
- private void setConnectionState(int state) {
+ @VisibleForTesting
+ void setConnectionState(int state) {
int oldState = -1;
synchronized (this) {
oldState = mState;
@@ -428,7 +430,8 @@ public class AvrcpBipClient {
}
}
- private String getStateName() {
+ @VisibleForTesting
+ String getStateName() {
int state = getState();
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index c9fd66fdff920c8841d521f06443a46c88a21280..91efbe61ef077e86b242659f74a17e641d2a62b0 100755
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -23,7 +23,6 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothAvrcpController;
import android.content.AttributionSource;
-import android.content.ComponentName;
import android.content.Intent;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.PlaybackStateCompat;
@@ -36,6 +35,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
import java.util.ArrayList;
@@ -68,7 +68,8 @@ public class AvrcpControllerService extends ProfileService {
private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
- private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
+ @VisibleForTesting
+ static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
private static final byte JNI_PLAY_STATUS_ERROR = -1;
/* Folder/Media Item scopes.
@@ -174,6 +175,7 @@ public class AvrcpControllerService extends ProfileService {
for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
stateMachine.quitNow();
}
+ mDeviceStateMap.clear();
sService = null;
sBrowseTree = null;
@@ -202,7 +204,8 @@ public class AvrcpControllerService extends ProfileService {
/**
* Set the current active device, notify devices of activity status
*/
- private boolean setActiveDevice(BluetoothDevice device) {
+ @VisibleForTesting
+ boolean setActiveDevice(BluetoothDevice device) {
A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
if (a2dpSinkService == null) {
return false;
@@ -278,7 +281,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void refreshContents(BrowseTree.BrowseNode node) {
+ @VisibleForTesting
+ void refreshContents(BrowseTree.BrowseNode node) {
BluetoothDevice device = node.getDevice();
if (device == null) {
return;
@@ -360,14 +364,18 @@ public class AvrcpControllerService extends ProfileService {
}
//Binder object: Must be static class or memory leak may occur
- private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
+ @VisibleForTesting
+ static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
private AvrcpControllerService mService;
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private AvrcpControllerService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
}
@@ -468,14 +476,16 @@ public class AvrcpControllerService extends ProfileService {
/* JNI API*/
// Called by JNI when a passthrough key was received.
- private void handlePassthroughRsp(int id, int keyState, byte[] address) {
+ @VisibleForTesting
+ void handlePassthroughRsp(int id, int keyState, byte[] address) {
if (DBG) {
Log.d(TAG, "passthrough response received as: key: " + id
- + " state: " + keyState + "address:" + address);
+ + " state: " + keyState + "address:" + Arrays.toString(address));
}
}
- private void handleGroupNavigationRsp(int id, int keyState) {
+ @VisibleForTesting
+ void handleGroupNavigationRsp(int id, int keyState) {
if (DBG) {
Log.d(TAG, "group navigation response received as: key: " + id + " state: "
+ keyState);
@@ -483,17 +493,14 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI when a device has connected or disconnected.
- private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
+ @VisibleForTesting
+ synchronized void onConnectionStateChanged(boolean remoteControlConnected,
boolean browsingConnected, byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
if (DBG) {
Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
+ browsingConnected + device);
}
- if (device == null) {
- Log.e(TAG, "onConnectionStateChanged Device is null");
- return;
- }
StackEvent event =
StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
@@ -513,12 +520,14 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI to notify Avrcp of features supported by the Remote device.
- private void getRcFeatures(byte[] address, int features) {
+ @VisibleForTesting
+ void getRcFeatures(byte[] address, int features) {
/* Do Nothing. */
}
// Called by JNI to notify Avrcp of a remote device's Cover Art PSM
- private void getRcPsm(byte[] address, int psm) {
+ @VisibleForTesting
+ void getRcPsm(byte[] address, int psm) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")");
AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
@@ -529,12 +538,14 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI
- private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
+ @VisibleForTesting
+ void setPlayerAppSettingRsp(byte[] address, byte accepted) {
/* Do Nothing. */
}
// Called by JNI when remote wants to receive absolute volume notifications.
- private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
+ @VisibleForTesting
+ synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
if (DBG) {
Log.d(TAG, "handleRegisterNotificationAbsVol");
}
@@ -547,7 +558,8 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI when remote wants to set absolute volume.
- private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
+ @VisibleForTesting
+ synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
if (DBG) {
Log.d(TAG, "handleSetAbsVolume ");
}
@@ -560,7 +572,8 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI when a track changes and local AvrcpController is registered for updates.
- private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
+ @VisibleForTesting
+ synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
String[] attribVals) {
if (DBG) {
Log.d(TAG, "onTrackChanged");
@@ -587,7 +600,8 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI periodically based upon timer to update play position
- private synchronized void onPlayPositionChanged(byte[] address, int songLen,
+ @VisibleForTesting
+ synchronized void onPlayPositionChanged(byte[] address, int songLen,
int currSongPosition) {
if (DBG) {
Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
@@ -602,7 +616,8 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI on changes of play status
- private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
+ @VisibleForTesting
+ synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
if (DBG) {
Log.d(TAG, "onPlayStatusChanged " + playStatus);
}
@@ -616,7 +631,8 @@ public class AvrcpControllerService extends ProfileService {
}
// Called by JNI to report remote Player's capabilities
- private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
+ @VisibleForTesting
+ synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
int rspLen) {
if (DBG) {
Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
@@ -632,7 +648,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
+ @VisibleForTesting
+ synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
int rspLen) {
if (DBG) {
Log.d(TAG, "onPlayerAppSettingChanged ");
@@ -649,7 +666,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void onAvailablePlayerChanged(byte[] address) {
+ @VisibleForTesting
+ void onAvailablePlayerChanged(byte[] address) {
if (DBG) {
Log.d(TAG," onAvailablePlayerChanged");
}
@@ -712,7 +730,8 @@ public class AvrcpControllerService extends ProfileService {
int[] attrIds, String[] attrVals) {
if (VDBG) {
Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type: " + type + " name: " + name
- + " attrids: " + attrIds + " attrVals: " + attrVals);
+ + " attrids: " + Arrays.toString(attrIds)
+ + " attrVals: " + Arrays.toString(attrVals));
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
@@ -750,10 +769,10 @@ public class AvrcpControllerService extends ProfileService {
AvrcpPlayer createFromNativePlayerItem(byte[] address, int id, String name,
byte[] transportFlags, int playStatus, int playerType) {
if (VDBG) {
- Log.d(TAG,
- "createFromNativePlayerItem name: " + name + " transportFlags "
- + transportFlags + " play status " + playStatus + " player type "
- + playerType);
+ Log.d(TAG, "createFromNativePlayerItem name: " + name
+ + " transportFlags " + Arrays.toString(transportFlags)
+ + " play status " + playStatus
+ + " player type " + playerType);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
@@ -766,7 +785,8 @@ public class AvrcpControllerService extends ProfileService {
return apb.build();
}
- private void handleChangeFolderRsp(byte[] address, int count) {
+ @VisibleForTesting
+ void handleChangeFolderRsp(byte[] address, int count) {
if (DBG) {
Log.d(TAG, "handleChangeFolderRsp count: " + count);
}
@@ -778,7 +798,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
+ @VisibleForTesting
+ void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
if (DBG) {
Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
}
@@ -791,7 +812,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void handleSetAddressedPlayerRsp(byte[] address, int status) {
+ @VisibleForTesting
+ void handleSetAddressedPlayerRsp(byte[] address, int status) {
if (DBG) {
Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
}
@@ -804,7 +826,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void handleAddressedPlayerChanged(byte[] address, int id) {
+ @VisibleForTesting
+ void handleAddressedPlayerChanged(byte[] address, int id) {
if (DBG) {
Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
}
@@ -817,7 +840,8 @@ public class AvrcpControllerService extends ProfileService {
}
}
- private void handleNowPlayingContentChanged(byte[] address) {
+ @VisibleForTesting
+ void handleNowPlayingContentChanged(byte[] address) {
if (DBG) {
Log.d(TAG, "handleNowPlayingContentChanged");
}
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 508c68cb91e433e32d3a86bebe74cc6ec1f9033c..ecfd441a6059040b7b76720c1243a55698d66272 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -23,6 +23,8 @@ import android.util.Log;
import com.android.bluetooth.Utils;
+import com.google.common.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -57,7 +59,8 @@ public class BrowseTree {
public static final String PLAYER_PREFIX = "PLAYER";
// Static instance of Folder ID <-> Folder Instance (for navigation purposes)
- private final HashMap mBrowseMap = new HashMap();
+ @VisibleForTesting
+ final HashMap mBrowseMap = new HashMap();
private BrowseNode mCurrentBrowseNode;
private BrowseNode mCurrentBrowsedPlayer;
private BrowseNode mCurrentAddressedPlayer;
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/android/app/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
index 362548e5fac1c0c3b56477c27ffb35ab11c51a50..90446b3ea279409914bef19e845a781390c59c97 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -20,6 +20,8 @@ import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
/*
@@ -39,20 +41,28 @@ class PlayerApplicationSettings {
private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
- private static final byte JNI_REPEAT_STATUS_OFF = 0x01;
- private static final byte JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
- private static final byte JNI_REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
- private static final byte JNI_REPEAT_STATUS_GROUP_REPEAT = 0x04;
-
- private static final byte JNI_SHUFFLE_STATUS_OFF = 0x01;
- private static final byte JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
- private static final byte JNI_SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+ @VisibleForTesting
+ static final byte JNI_REPEAT_STATUS_OFF = 0x01;
+ @VisibleForTesting
+ static final byte JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+ @VisibleForTesting
+ static final byte JNI_REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+ @VisibleForTesting
+ static final byte JNI_REPEAT_STATUS_GROUP_REPEAT = 0x04;
+
+ @VisibleForTesting
+ static final byte JNI_SHUFFLE_STATUS_OFF = 0x01;
+ @VisibleForTesting
+ static final byte JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+ @VisibleForTesting
+ static final byte JNI_SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
private static final byte JNI_SCAN_STATUS_OFF = 0x01;
private static final byte JNI_SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
private static final byte JNI_SCAN_STATUS_GROUP_SCAN = 0x03;
- private static final byte JNI_STATUS_INVALID = -1;
+ @VisibleForTesting
+ static final byte JNI_STATUS_INVALID = -1;
/*
* Hash map of current settings.
@@ -123,7 +133,8 @@ class PlayerApplicationSettings {
}
// Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
- private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
+ @VisibleForTesting
+ static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
if (attribId == REPEAT_STATUS) {
switch (attribVal) {
case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/bip/RequestGetImage.java b/android/app/src/com/android/bluetooth/avrcpcontroller/bip/RequestGetImage.java
index d6fcabf82ab2e79323f55393c29f3c171441efb4..fc5f49960b494d405d5ea3432ecafdfba814c711 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/bip/RequestGetImage.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/bip/RequestGetImage.java
@@ -16,6 +16,7 @@
package com.android.bluetooth.avrcpcontroller;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
@@ -29,7 +30,8 @@ import java.io.InputStream;
public class RequestGetImage extends BipRequest {
// Expected inputs
private final String mImageHandle;
- private final BipImageDescriptor mImageDescriptor;
+ @VisibleForTesting
+ final BipImageDescriptor mImageDescriptor;
// Expected return type
private static final String TYPE = "x-bt/img-img";
diff --git a/android/app/src/com/android/bluetooth/bas/BatteryService.java b/android/app/src/com/android/bluetooth/bas/BatteryService.java
index 43b4e766ffadc8badf7dc718017f51fc5caae6d0..71a5e645e3d49ca06c80a7ec4a69397288d269f8 100644
--- a/android/app/src/com/android/bluetooth/bas/BatteryService.java
+++ b/android/app/src/com/android/bluetooth/bas/BatteryService.java
@@ -214,6 +214,7 @@ public class BatteryService extends ProfileService {
BatteryStateMachine sm = getOrCreateStateMachine(device);
if (sm == null) {
Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+ return false;
}
sm.sendMessage(BatteryStateMachine.CONNECT);
}
@@ -527,9 +528,12 @@ public class BatteryService extends ProfileService {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private BatteryService getService(AttributionSource source) {
BatteryService service = mServiceRef.get();
+ if (Utils.isInstrumentationTestMode()) {
+ return service;
+ }
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(service, TAG)
+ if (!Utils.checkServiceAvailable(service, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(service, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return null;
}
diff --git a/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
index 878c71ee5a882168771201ce98863b1b1246193e..394f4172222d58878b0ff3e86a8e866c32df84c5 100644
--- a/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
@@ -18,7 +18,7 @@ package com.android.bluetooth.bas;
import static android.bluetooth.BluetoothDevice.PHY_LE_1M_MASK;
import static android.bluetooth.BluetoothDevice.PHY_LE_2M_MASK;
-import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO;
+import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -233,7 +233,7 @@ public class BatteryStateMachine extends StateMachine {
mBluetoothGatt.close();
}
mBluetoothGatt = mDevice.connectGatt(service, /*autoConnect=*/false,
- mGattCallback, TRANSPORT_AUTO, /*opportunistic=*/true,
+ mGattCallback, TRANSPORT_LE, /*opportunistic=*/true,
PHY_LE_1M_MASK | PHY_LE_2M_MASK, getHandler());
return mBluetoothGatt != null;
}
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
index d74aaf6640f140d0bd6d2a5ff07c0b5e92c84134..53c5c3466b763e2f418daaae5fa572da5c4d5b7f 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
@@ -56,6 +56,7 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -648,6 +649,8 @@ public class BassClientService extends ProfileService {
return;
}
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnecting device because it was unbonded.");
+ disconnect(device);
return;
}
removeStateMachine(device);
@@ -1041,6 +1044,7 @@ public class BassClientService extends ProfileService {
return;
}
+ byte[] code = sourceMetadata.getBroadcastCode();
for (BluetoothDevice device : devices) {
BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
if (stateMachine == null) {
@@ -1070,6 +1074,15 @@ public class BassClientService extends ProfileService {
BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION);
continue;
}
+ if ((code != null) && (code.length != 0)) {
+ if ((code.length > 16) || (code.length < 4)) {
+ log("Invalid broadcast code length: " + code.length
+ + ", should be between 4 and 16 octets");
+ mCallbacks.notifySourceAddFailed(device, sourceMetadata,
+ BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
+ continue;
+ }
+ }
if (isGroupOp) {
enqueueSourceGroupOp(device, BassClientStateMachine.ADD_BCAST_SOURCE,
@@ -1104,6 +1117,7 @@ public class BassClientService extends ProfileService {
return;
}
+ byte[] code = updatedMetadata.getBroadcastCode();
for (Map.Entry deviceSourceIdPair : devices.entrySet()) {
BluetoothDevice device = deviceSourceIdPair.getKey();
Integer deviceSourceId = deviceSourceIdPair.getValue();
@@ -1127,6 +1141,15 @@ public class BassClientService extends ProfileService {
BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
continue;
}
+ if ((code != null) && (code.length != 0)) {
+ if ((code.length > 16) || (code.length < 4)) {
+ log("Invalid broadcast code length: " + code.length
+ + ", should be between 4 and 16 octets");
+ mCallbacks.notifySourceModifyFailed(device, sourceId,
+ BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
+ continue;
+ }
+ }
if (stateMachine.hasPendingSourceOperation()) {
throw new IllegalStateException("modifySource: source operation already pending");
}
@@ -1240,6 +1263,25 @@ public class BassClientService extends ProfileService {
return stateMachine.getMaximumSourceCapacity();
}
+ boolean isLocalBroadcast(BluetoothLeBroadcastMetadata metaData) {
+ if (metaData == null) {
+ return false;
+ }
+
+ LeAudioService leAudioService = mServiceFactory.getLeAudioService();
+ if (leAudioService == null) {
+ return false;
+ }
+
+ boolean wasFound = leAudioService.getAllBroadcastMetadata()
+ .stream()
+ .anyMatch(meta -> {
+ return meta.getSourceAdvertisingSid() == metaData.getSourceAdvertisingSid();
+ });
+ log("isLocalBroadcast=" + wasFound);
+ return wasFound;
+ }
+
static void log(String msg) {
if (BassConstants.BASS_DBG) {
Log.d(TAG, msg);
@@ -1452,11 +1494,14 @@ public class BassClientService extends ProfileService {
@VisibleForTesting
static class BluetoothLeBroadcastAssistantBinder extends IBluetoothLeBroadcastAssistant.Stub
implements IProfileServiceBinder {
- private BassClientService mService;
+ BassClientService mService;
private BassClientService getService() {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)) {
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
return null;
}
return mService;
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
index 9e5206c3bfb4ad4ba38b57ca5382c68d7dc043ce..f8e8388f5c774727a863507c6356164a1400e74f 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
@@ -14,50 +14,11 @@
* limitations under the License.
*/
-/**
- * Bluetooth Bassclient StateMachine. There is one instance per remote device.
- * - "Disconnected" and "Connected" are steady states.
- * - "Connecting" and "Disconnecting" are transient states until the
- * connection / disconnection is completed.
- * - "ConnectedProcessing" is an intermediate state to ensure, there is only
- * one Gatt transaction from the profile at any point of time
- *
- *
- * (Disconnected)
- * | ^
- * CONNECT | | DISCONNECTED
- * V |
- * (Connecting)<--->(Disconnecting)
- * | ^
- * CONNECTED | | DISCONNECT
- * V |
- * (Connected)
- * | ^
- * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT
- * V |
- * (ConnectedProcessing)
- * NOTES:
- * - If state machine is in "Connecting" state and the remote device sends
- * DISCONNECT request, the state machine transitions to "Disconnecting" state.
- * - Similarly, if the state machine is in "Disconnecting" state and the remote device
- * sends CONNECT request, the state machine transitions to "Connecting" state.
- * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and
- * all other requests (add, update, remove source) operations will be deferred in
- * "ConnectedProcessing" state
- * - Once the gatt transaction is done (or after a specified timeout of no response),
- * State machine will move back to "Connected" and try to process the deferred requests
- * as needed
- *
- * DISCONNECT
- * (Connecting) ---------------> (Disconnecting)
- * <---------------
- * CONNECT
- *
- */
package com.android.bluetooth.bass_client;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -87,6 +48,7 @@ import android.os.ParcelUuid;
import android.provider.DeviceConfig;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
@@ -106,13 +68,16 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
+import java.util.UUID;
import java.util.stream.IntStream;
@VisibleForTesting
public class BassClientStateMachine extends StateMachine {
private static final String TAG = "BassClientStateMachine";
- private static final byte[] REMOTE_SCAN_STOP = {00};
- private static final byte[] REMOTE_SCAN_START = {01};
+ @VisibleForTesting
+ static final byte[] REMOTE_SCAN_STOP = {00};
+ @VisibleForTesting
+ static final byte[] REMOTE_SCAN_START = {01};
private static final byte OPCODE_ADD_SOURCE = 0x02;
private static final byte OPCODE_UPDATE_SOURCE = 0x03;
private static final byte OPCODE_SET_BCAST_PIN = 0x04;
@@ -148,45 +113,56 @@ public class BassClientStateMachine extends StateMachine {
private final Disconnected mDisconnected = new Disconnected();
private final Connected mConnected = new Connected();
private final Connecting mConnecting = new Connecting();
- private final Disconnecting mDisconnecting = new Disconnecting();
private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing();
- private final List mBroadcastCharacteristics =
+ @VisibleForTesting
+ final List mBroadcastCharacteristics =
new ArrayList();
@VisibleForTesting
BluetoothDevice mDevice;
private boolean mIsAllowedList = false;
private int mLastConnectionState = -1;
- private boolean mMTUChangeRequested = false;
- private boolean mDiscoveryInitiated = false;
+ @VisibleForTesting
+ boolean mMTUChangeRequested = false;
+ @VisibleForTesting
+ boolean mDiscoveryInitiated = false;
@VisibleForTesting
BassClientService mService;
-
- private BluetoothGattCharacteristic mBroadcastScanControlPoint;
+ @VisibleForTesting
+ BluetoothGattCharacteristic mBroadcastScanControlPoint;
private boolean mFirstTimeBisDiscovery = false;
private int mPASyncRetryCounter = 0;
private ScanResult mScanRes = null;
- private int mNumOfBroadcastReceiverStates = 0;
+ @VisibleForTesting
+ int mNumOfBroadcastReceiverStates = 0;
private BluetoothAdapter mBluetoothAdapter =
BluetoothAdapter.getDefaultAdapter();
private ServiceFactory mFactory = new ServiceFactory();
- private int mPendingOperation = -1;
- private byte mPendingSourceId = -1;
- private BluetoothLeBroadcastMetadata mPendingMetadata = null;
+ @VisibleForTesting
+ int mPendingOperation = -1;
+ @VisibleForTesting
+ byte mPendingSourceId = -1;
+ @VisibleForTesting
+ BluetoothLeBroadcastMetadata mPendingMetadata = null;
private BluetoothLeBroadcastReceiveState mSetBroadcastPINRcvState = null;
- private boolean mSetBroadcastCodePending = false;
+ @VisibleForTesting
+ boolean mSetBroadcastCodePending = false;
private final Map mPendingRemove = new HashMap();
// Psync and PAST interfaces
private PeriodicAdvertisingManager mPeriodicAdvManager;
private boolean mAutoAssist = false;
- private boolean mAutoTriggered = false;
- private boolean mNoStopScanOffload = false;
+ @VisibleForTesting
+ boolean mAutoTriggered = false;
+ @VisibleForTesting
+ boolean mNoStopScanOffload = false;
private boolean mDefNoPAS = false;
private boolean mForceSB = false;
private int mBroadcastSourceIdLength = 3;
- private byte mNextSourceId = 0;
-
- BluetoothGatt mBluetoothGatt = null;
+ @VisibleForTesting
+ byte mNextSourceId = 0;
+ private boolean mAllowReconnect = false;
+ @VisibleForTesting
+ BluetoothGattTestableWrapper mBluetoothGatt = null;
BluetoothGattCallback mGattCallback = null;
BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper,
@@ -196,7 +172,6 @@ public class BassClientStateMachine extends StateMachine {
mService = svc;
mConnectTimeoutMs = connectTimeoutMs;
addState(mDisconnected);
- addState(mDisconnecting);
addState(mConnected);
addState(mConnecting);
addState(mConnectedProcessing);
@@ -351,7 +326,8 @@ public class BassClientStateMachine extends StateMachine {
Map bmsAdvDataMap = record.getServiceData();
if (bmsAdvDataMap != null) {
for (Map.Entry entry : bmsAdvDataMap.entrySet()) {
- log("ParcelUUid = " + entry.getKey() + ", Value = " + entry.getValue());
+ log("ParcelUUid = " + entry.getKey() + ", Value = "
+ + Arrays.toString(entry.getValue()));
}
}
byte[] advData = record.getServiceData(BassConstants.BASIC_AUDIO_UUID);
@@ -381,8 +357,9 @@ public class BassClientStateMachine extends StateMachine {
mNoStopScanOffload = true;
cancelActiveSync(null);
try {
- mPeriodicAdvManager.registerSync(scanRes, 0,
- BassConstants.PSYNC_TIMEOUT, mPeriodicAdvCallback);
+ BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync(
+ mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT,
+ mPeriodicAdvCallback, null);
} catch (IllegalArgumentException ex) {
Log.w(TAG, "registerSync:IllegalArgumentException");
Message message = obtainMessage(STOP_SCAN_OFFLOAD);
@@ -556,7 +533,8 @@ public class BassClientStateMachine extends StateMachine {
mService.getCallbacks().notifyReceiveStateChanged(mDevice, sourceId, state);
}
- private static boolean isEmpty(final byte[] data) {
+ @VisibleForTesting
+ static boolean isEmpty(final byte[] data) {
return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0);
}
@@ -587,13 +565,29 @@ public class BassClientStateMachine extends StateMachine {
& (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
log("Initiate PAST for: " + mDevice + ", syncHandle: " + syncHandle
+ "serviceData" + serviceData);
- mPeriodicAdvManager.transferSync(mDevice, serviceData, syncHandle);
+ BluetoothMethodProxy.getInstance().periodicAdvertisingManagerTransferSync(
+ mPeriodicAdvManager, mDevice, serviceData, syncHandle);
}
} else {
- Log.e(TAG, "There is no valid sync handle for this Source");
- if (mAutoAssist) {
- //initiate Auto Assist procedure for this device
- mService.getBassUtils().triggerAutoAssist(recvState);
+ if (mService.isLocalBroadcast(mPendingMetadata)) {
+ int advHandle = mPendingMetadata.getSourceAdvertisingSid();
+ serviceData = 0x000000FF & recvState.getSourceId();
+ serviceData = serviceData << 8;
+ // Address we set in the Source Address can differ from the address in the air
+ serviceData = serviceData
+ | BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS;
+ log("Initiate local broadcast PAST for: " + mDevice
+ + ", advSID/Handle: " + advHandle
+ + ", serviceData: " + serviceData);
+ BluetoothMethodProxy.getInstance().periodicAdvertisingManagerTransferSetInfo(
+ mPeriodicAdvManager, mDevice, serviceData, advHandle,
+ mPeriodicAdvCallback);
+ } else {
+ Log.e(TAG, "There is no valid sync handle for this Source");
+ if (mAutoAssist) {
+ // Initiate Auto Assist procedure for this device
+ mService.getBassUtils().triggerAutoAssist(recvState);
+ }
}
}
} else if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
@@ -734,6 +728,7 @@ public class BassClientStateMachine extends StateMachine {
BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE);
byte sourceAddressType = receiverState[BassConstants
.BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX];
+ BassUtils.reverse(sourceAddress);
String address = Utils.getAddressStringFromByte(sourceAddress);
BluetoothDevice device = btAdapter.getRemoteLeDevice(
address, sourceAddressType);
@@ -750,18 +745,18 @@ public class BassClientStateMachine extends StateMachine {
numSubGroups,
audioSyncState,
metadataList);
- log("Receiver state: " +
- "\n\tSource ID: " + sourceId +
- "\n\tSource Address Type: " + (int) sourceAddressType +
- "\n\tDevice: " + device +
- "\n\tSource Adv SID: " + sourceAdvSid +
- "\n\tBroadcast ID: " + broadcastId +
- "\n\tMetadata Sync State: " + (int) metaDataSyncState +
- "\n\tEncryption Status: " + (int) encryptionStatus +
- "\n\tBad Broadcast Code: " + badBroadcastCode +
- "\n\tNumber Of Subgroups: " + numSubGroups +
- "\n\tAudio Sync State: " + audioSyncState +
- "\n\tMetadata: " + metadataList);
+ log("Receiver state: "
+ + "\n\tSource ID: " + sourceId
+ + "\n\tSource Address Type: " + (int) sourceAddressType
+ + "\n\tDevice: " + device
+ + "\n\tSource Adv SID: " + sourceAdvSid
+ + "\n\tBroadcast ID: " + broadcastId
+ + "\n\tMetadata Sync State: " + (int) metaDataSyncState
+ + "\n\tEncryption Status: " + (int) encryptionStatus
+ + "\n\tBad Broadcast Code: " + Arrays.toString(badBroadcastCode)
+ + "\n\tNumber Of Subgroups: " + numSubGroups
+ + "\n\tAudio Sync State: " + audioSyncState
+ + "\n\tMetadata: " + metadataList);
}
return recvState;
}
@@ -771,9 +766,11 @@ public class BassClientStateMachine extends StateMachine {
log("processBroadcastReceiverState: characteristic:" + characteristic);
BluetoothLeBroadcastReceiveState recvState = parseBroadcastReceiverState(
receiverState);
- if (recvState == null || recvState.getSourceId() == -1) {
- log("Null recvState or processBroadcastReceiverState: invalid index: "
- + recvState.getSourceId());
+ if (recvState == null) {
+ log("processBroadcastReceiverState: Null recvState");
+ return;
+ } else if (recvState.getSourceId() == -1) {
+ log("processBroadcastReceiverState: invalid index: " + recvState.getSourceId());
return;
}
BluetoothLeBroadcastReceiveState oldRecvState =
@@ -834,157 +831,157 @@ public class BassClientStateMachine extends StateMachine {
// Implements callback methods for GATT events that the app cares about.
// For example, connection change and services discovered.
final class GattCallback extends BluetoothGattCallback {
- @Override
- public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
- boolean isStateChanged = false;
- log("onConnectionStateChange : Status=" + status + "newState" + newState);
- if (newState == BluetoothProfile.STATE_CONNECTED
- && getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
- isStateChanged = true;
- Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice);
- if (mService.okToConnect(mDevice)) {
- log("Bassclient Connected to: " + mDevice);
- if (mBluetoothGatt != null) {
- log("Attempting to start service discovery:"
- + mBluetoothGatt.discoverServices());
- mDiscoveryInitiated = true;
- }
- } else if (mBluetoothGatt != null) {
- // Reject the connection
- Log.w(TAG, "Bassclient Connect request rejected: " + mDevice);
- mBluetoothGatt.disconnect();
- mBluetoothGatt.close();
- mBluetoothGatt = null;
- // force move to disconnected
- newState = BluetoothProfile.STATE_DISCONNECTED;
- }
- } else if (newState == BluetoothProfile.STATE_DISCONNECTED
- && getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
- isStateChanged = true;
- log("Disconnected from Bass GATT server.");
- }
- if (isStateChanged) {
- Message m = obtainMessage(CONNECTION_STATE_CHANGED);
- m.obj = newState;
- sendMessage(m);
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ boolean isStateChanged = false;
+ log("onConnectionStateChange : Status=" + status + "newState" + newState);
+ if (newState == BluetoothProfile.STATE_CONNECTED
+ && getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+ isStateChanged = true;
+ Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice);
+ if (mService.okToConnect(mDevice)) {
+ log("Bassclient Connected to: " + mDevice);
+ if (mBluetoothGatt != null) {
+ log("Attempting to start service discovery:"
+ + mBluetoothGatt.discoverServices());
+ mDiscoveryInitiated = true;
}
+ } else if (mBluetoothGatt != null) {
+ // Reject the connection
+ Log.w(TAG, "Bassclient Connect request rejected: " + mDevice);
+ mBluetoothGatt.disconnect();
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ // force move to disconnected
+ newState = BluetoothProfile.STATE_DISCONNECTED;
}
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED
+ && getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+ isStateChanged = true;
+ log("Disconnected from Bass GATT server.");
+ }
+ if (isStateChanged) {
+ Message m = obtainMessage(CONNECTION_STATE_CHANGED);
+ m.obj = newState;
+ sendMessage(m);
+ }
+ }
- @Override
- public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- log("onServicesDiscovered:" + status);
- if (mDiscoveryInitiated) {
- mDiscoveryInitiated = false;
- if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) {
- mBluetoothGatt.requestMtu(BassConstants.BASS_MAX_BYTES);
- mMTUChangeRequested = true;
- } else {
- Log.w(TAG, "onServicesDiscovered received: "
- + status + "mBluetoothGatt" + mBluetoothGatt);
- }
- } else {
- log("remote initiated callback");
- }
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ log("onServicesDiscovered:" + status);
+ if (mDiscoveryInitiated) {
+ mDiscoveryInitiated = false;
+ if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) {
+ mBluetoothGatt.requestMtu(BassConstants.BASS_MAX_BYTES);
+ mMTUChangeRequested = true;
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: "
+ + status + "mBluetoothGatt" + mBluetoothGatt);
}
+ } else {
+ log("remote initiated callback");
+ }
+ }
- @Override
- public void onCharacteristicRead(
- BluetoothGatt gatt,
- BluetoothGattCharacteristic characteristic,
- int status) {
- log("onCharacteristicRead:: status: " + status + "char:" + characteristic);
- if (status == BluetoothGatt.GATT_SUCCESS && characteristic.getUuid()
- .equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
- log("onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status);
- logByteArray("Received ", characteristic.getValue(), 0,
- characteristic.getValue().length);
- if (characteristic.getValue() == null) {
- Log.e(TAG, "Remote receiver state is NULL");
- return;
- }
- processBroadcastReceiverState(characteristic.getValue(), characteristic);
- }
- // switch to receiving notifications after initial characteristic read
- BluetoothGattDescriptor desc = characteristic
- .getDescriptor(BassConstants.CLIENT_CHARACTERISTIC_CONFIG);
- if (mBluetoothGatt != null && desc != null) {
- log("Setting the value for Desc");
- mBluetoothGatt.setCharacteristicNotification(characteristic, true);
- desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
- mBluetoothGatt.writeDescriptor(desc);
- } else {
- Log.w(TAG, "CCC for " + characteristic + "seem to be not present");
- // at least move the SM to stable state
- Message m = obtainMessage(GATT_TXN_PROCESSED);
- m.arg1 = status;
- sendMessage(m);
- }
+ @Override
+ public void onCharacteristicRead(
+ BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ log("onCharacteristicRead:: status: " + status + "char:" + characteristic);
+ if (status == BluetoothGatt.GATT_SUCCESS && characteristic.getUuid()
+ .equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
+ log("onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status);
+ if (characteristic.getValue() == null) {
+ Log.e(TAG, "Remote receiver state is NULL");
+ return;
}
+ logByteArray("Received ", characteristic.getValue(), 0,
+ characteristic.getValue().length);
+ processBroadcastReceiverState(characteristic.getValue(), characteristic);
+ }
+ // switch to receiving notifications after initial characteristic read
+ BluetoothGattDescriptor desc = characteristic
+ .getDescriptor(BassConstants.CLIENT_CHARACTERISTIC_CONFIG);
+ if (mBluetoothGatt != null && desc != null) {
+ log("Setting the value for Desc");
+ mBluetoothGatt.setCharacteristicNotification(characteristic, true);
+ desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(desc);
+ } else {
+ Log.w(TAG, "CCC for " + characteristic + "seem to be not present");
+ // at least move the SM to stable state
+ Message m = obtainMessage(GATT_TXN_PROCESSED);
+ m.arg1 = status;
+ sendMessage(m);
+ }
+ }
- @Override
- public void onDescriptorWrite(
- BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
- log("onDescriptorWrite");
- if (status == BluetoothGatt.GATT_SUCCESS
- && descriptor.getUuid()
- .equals(BassConstants.CLIENT_CHARACTERISTIC_CONFIG)) {
- log("CCC write resp");
- }
+ @Override
+ public void onDescriptorWrite(
+ BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ log("onDescriptorWrite");
+ if (status == BluetoothGatt.GATT_SUCCESS
+ && descriptor.getUuid()
+ .equals(BassConstants.CLIENT_CHARACTERISTIC_CONFIG)) {
+ log("CCC write resp");
+ }
- // Move the SM to connected so further reads happens
- Message m = obtainMessage(GATT_TXN_PROCESSED);
- m.arg1 = status;
- sendMessage(m);
- }
+ // Move the SM to connected so further reads happens
+ Message m = obtainMessage(GATT_TXN_PROCESSED);
+ m.arg1 = status;
+ sendMessage(m);
+ }
- @Override
- public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
- log("onMtuChanged: mtu:" + mtu);
- if (mMTUChangeRequested && mBluetoothGatt != null) {
- acquireAllBassChars();
- mMTUChangeRequested = false;
- } else {
- log("onMtuChanged is remote initiated trigger, mBluetoothGatt:"
- + mBluetoothGatt);
- }
- }
+ @Override
+ public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+ log("onMtuChanged: mtu:" + mtu);
+ if (mMTUChangeRequested && mBluetoothGatt != null) {
+ acquireAllBassChars();
+ mMTUChangeRequested = false;
+ } else {
+ log("onMtuChanged is remote initiated trigger, mBluetoothGatt:"
+ + mBluetoothGatt);
+ }
+ }
- @Override
- public void onCharacteristicChanged(
- BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
- log("onCharacteristicChanged :: " + characteristic.getUuid().toString());
- if (characteristic.getUuid().equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
- log("onCharacteristicChanged is rcvr State :: "
- + characteristic.getUuid().toString());
- if (characteristic.getValue() == null) {
- Log.e(TAG, "Remote receiver state is NULL");
- return;
- }
- logByteArray("onCharacteristicChanged: Received ",
- characteristic.getValue(),
- 0,
- characteristic.getValue().length);
- processBroadcastReceiverState(characteristic.getValue(), characteristic);
- }
+ @Override
+ public void onCharacteristicChanged(
+ BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ log("onCharacteristicChanged :: " + characteristic.getUuid().toString());
+ if (characteristic.getUuid().equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
+ log("onCharacteristicChanged is rcvr State :: "
+ + characteristic.getUuid().toString());
+ if (characteristic.getValue() == null) {
+ Log.e(TAG, "Remote receiver state is NULL");
+ return;
}
+ logByteArray("onCharacteristicChanged: Received ",
+ characteristic.getValue(),
+ 0,
+ characteristic.getValue().length);
+ processBroadcastReceiverState(characteristic.getValue(), characteristic);
+ }
+ }
- @Override
- public void onCharacteristicWrite(
- BluetoothGatt gatt,
- BluetoothGattCharacteristic characteristic,
- int status) {
- log("onCharacteristicWrite: " + characteristic.getUuid().toString()
- + "status:" + status);
- if (status == 0
- && characteristic.getUuid()
- .equals(BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) {
- log("BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully");
- }
- Message m = obtainMessage(GATT_TXN_PROCESSED);
- m.arg1 = status;
- sendMessage(m);
- }
- };
+ @Override
+ public void onCharacteristicWrite(
+ BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ log("onCharacteristicWrite: " + characteristic.getUuid().toString()
+ + "status:" + status);
+ if (status == 0
+ && characteristic.getUuid()
+ .equals(BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) {
+ log("BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully");
+ }
+ Message m = obtainMessage(GATT_TXN_PROCESSED);
+ m.arg1 = status;
+ sendMessage(m);
+ }
+ }
/**
* Connects to the GATT server of the device.
@@ -997,11 +994,16 @@ public class BassClientStateMachine extends StateMachine {
mGattCallback = new GattCallback();
}
- mBluetoothGatt = mDevice.connectGatt(mService, autoConnect,
+ BluetoothGatt gatt = mDevice.connectGatt(mService, autoConnect,
mGattCallback, BluetoothDevice.TRANSPORT_LE,
(BluetoothDevice.PHY_LE_1M_MASK
| BluetoothDevice.PHY_LE_2M_MASK
| BluetoothDevice.PHY_LE_CODED_MASK), null);
+
+ if (gatt != null) {
+ mBluetoothGatt = new BluetoothGattTestableWrapper(gatt);
+ }
+
return mBluetoothGatt != null;
}
@@ -1077,7 +1079,7 @@ public class BassClientStateMachine extends StateMachine {
mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTED);
if (mLastConnectionState != BluetoothProfile.STATE_DISCONNECTED) {
// Reconnect in background if not disallowed by the service
- if (mService.okToConnect(mDevice)) {
+ if (mService.okToConnect(mDevice) && mAllowReconnect) {
connectGatt(false);
}
}
@@ -1104,6 +1106,7 @@ public class BassClientStateMachine extends StateMachine {
mBluetoothGatt.close();
mBluetoothGatt = null;
}
+ mAllowReconnect = true;
if (connectGatt(mIsAllowedList)) {
transitionTo(mConnecting);
} else {
@@ -1112,6 +1115,7 @@ public class BassClientStateMachine extends StateMachine {
break;
case DISCONNECT:
// Disconnect if there's an ongoing background connection
+ mAllowReconnect = false;
if (mBluetoothGatt != null) {
log("Cancelling the background connection to " + mDevice);
mBluetoothGatt.disconnect();
@@ -1208,24 +1212,16 @@ public class BassClientStateMachine extends StateMachine {
}
}
- private byte[] bluetoothAddressToBytes(String s) {
- log("BluetoothAddressToBytes: input string:" + s);
- String[] splits = s.split(":");
- byte[] addressBytes = new byte[6];
- for (int i = 0; i < 6; i++) {
- int hexValue = Integer.parseInt(splits[i], 16);
- log("hexValue:" + hexValue);
- addressBytes[i] = (byte) hexValue;
- }
- return addressBytes;
- }
-
private static int getBisSyncFromChannelPreference(
List channels) {
int bisSync = 0;
for (BluetoothLeBroadcastChannel channel : channels) {
if (channel.isSelected()) {
- bisSync |= 1 << channel.getChannelIndex();
+ if (channel.getChannelIndex() == 0) {
+ Log.e(TAG, "getBisSyncFromChannelPreference: invalid channel index=0");
+ continue;
+ }
+ bisSync |= 1 << (channel.getChannelIndex() - 1);
}
}
@@ -1243,7 +1239,9 @@ public class BassClientStateMachine extends StateMachine {
stream.write(metaData.getSourceAddressType());
// Advertiser_Address
- stream.write(Utils.getBytesFromAddress(advSource.getAddress()), 0, 6);
+ byte[] bcastSourceAddr = Utils.getBytesFromAddress(advSource.getAddress());
+ BassUtils.reverse(bcastSourceAddr);
+ stream.write(bcastSourceAddr, 0, 6);
log("Address bytes: " + advSource.getAddress());
// Advertising_SID
@@ -1290,16 +1288,6 @@ public class BassClientStateMachine extends StateMachine {
stream.write(metadata.getRawMetadata(), 0, metadata.getRawMetadata().length);
}
- if (metaData.isEncrypted() && metaData.getBroadcastCode().length == 16) {
- if (metaData.getBroadcastCode().length != 16) {
- Log.e(TAG, "Delivered invalid length of broadcast code: " +
- metaData.getBroadcastCode().length + ", should be 16");
- return null;
- }
-
- mSetBroadcastCodePending = true;
- }
-
byte[] res = stream.toByteArray();
log("ADD_BCAST_SOURCE in Bytes");
BassUtils.printByteArray(res);
@@ -1373,15 +1361,6 @@ public class BassClientStateMachine extends StateMachine {
return res;
}
- private byte[] convertAsciitoValues(byte[] val) {
- byte[] ret = new byte[val.length];
- for (int i = 0; i < val.length; i++) {
- ret[i] = (byte) (val[i] - (byte) '0');
- }
- log("convertAsciitoValues: returns:" + Arrays.toString(val));
- return ret;
- }
-
private byte[] convertRecvStateToSetBroadcastCodeByteArray(
BluetoothLeBroadcastReceiveState recvState) {
byte[] res = new byte[BassConstants.PIN_CODE_CMD_LEN];
@@ -1398,10 +1377,8 @@ public class BassClientStateMachine extends StateMachine {
+ recvState.getSourceId());
return null;
}
- // Can Keep as ASCII as is
- String reversePIN = new StringBuffer(new String(metaData.getBroadcastCode()))
- .reverse().toString();
- byte[] actualPIN = reversePIN.getBytes();
+ // Broadcast Code
+ byte[] actualPIN = metaData.getBroadcastCode();
if (actualPIN == null) {
Log.e(TAG, "actual PIN is null");
return null;
@@ -1409,6 +1386,8 @@ public class BassClientStateMachine extends StateMachine {
log("byte array broadcast Code:" + Arrays.toString(actualPIN));
log("pinLength:" + actualPIN.length);
// Broadcast_Code, Fill the PIN code in the Last Position
+ // This effectively adds padding zeros to LSB positions when the broadcast code
+ // is shorter than 16 octets
System.arraycopy(
actualPIN, 0, res,
(BassConstants.PIN_CODE_CMD_LEN - actualPIN.length), actualPIN.length);
@@ -1434,8 +1413,7 @@ public class BassClientStateMachine extends StateMachine {
continue;
}
if (sourceId == state.getSourceId() && state.getBigEncryptionState()
- == BluetoothLeBroadcastReceiveState
- .BIG_ENCRYPTION_STATE_CODE_REQUIRED) {
+ == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED) {
retval = true;
break;
}
@@ -1453,6 +1431,12 @@ public class BassClientStateMachine extends StateMachine {
removeDeferredMessages(CONNECT);
if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) {
log("CONNECTED->CONNECTED: Ignore");
+ // Broadcast for testing purpose only
+ if (Utils.isInstrumentationTestMode()) {
+ Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
+ mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+ Utils.getTempAllowlistBroadcastOptions());
+ }
} else {
broadcastConnectionState(mDevice, mLastConnectionState,
BluetoothProfile.STATE_CONNECTED);
@@ -1476,6 +1460,7 @@ public class BassClientStateMachine extends StateMachine {
break;
case DISCONNECT:
log("Disconnecting from " + mDevice);
+ mAllowReconnect = false;
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
@@ -1545,6 +1530,9 @@ public class BassClientStateMachine extends StateMachine {
mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
mPendingOperation = message.what;
mPendingMetadata = metaData;
+ if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) {
+ mSetBroadcastCodePending = true;
+ }
transitionTo(mConnectedProcessing);
sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
} else {
@@ -1572,6 +1560,9 @@ public class BassClientStateMachine extends StateMachine {
if (paSync == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE) {
setPendingRemove(sourceId, true);
}
+ if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) {
+ mSetBroadcastCodePending = true;
+ }
mPendingMetadata = metaData;
transitionTo(mConnectedProcessing);
sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
@@ -1584,7 +1575,6 @@ public class BassClientStateMachine extends StateMachine {
case SET_BCAST_CODE:
BluetoothLeBroadcastReceiveState recvState =
(BluetoothLeBroadcastReceiveState) message.obj;
- sourceId = message.arg2;
log("SET_BCAST_CODE metaData: " + recvState);
if (!isItRightTimeToUpdateBroadcastPin((byte) recvState.getSourceId())) {
mSetBroadcastCodePending = true;
@@ -1705,12 +1695,20 @@ public class BassClientStateMachine extends StateMachine {
}
}
+ // public for testing, but private for non-testing
@VisibleForTesting
class ConnectedProcessing extends State {
@Override
public void enter() {
log("Enter ConnectedProcessing(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
+
+ // Broadcast for testing purpose only
+ if (Utils.isInstrumentationTestMode()) {
+ Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
+ mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+ Utils.getTempAllowlistBroadcastOptions());
+ }
}
@Override
public void exit() {
@@ -1732,6 +1730,7 @@ public class BassClientStateMachine extends StateMachine {
break;
case DISCONNECT:
Log.w(TAG, "DISCONNECT requested!: " + mDevice);
+ mAllowReconnect = false;
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
@@ -1772,7 +1771,7 @@ public class BassClientStateMachine extends StateMachine {
transitionTo(mConnected);
break;
case GATT_TXN_TIMEOUT:
- log("GATT transaction timedout for" + mDevice);
+ log("GATT transaction timeout for" + mDevice);
sendPendingCallbacks(
mPendingOperation,
BluetoothStatusCodes.ERROR_UNKNOWN);
@@ -1798,67 +1797,6 @@ public class BassClientStateMachine extends StateMachine {
}
}
- @VisibleForTesting
- class Disconnecting extends State {
- @Override
- public void enter() {
- log("Enter Disconnecting(" + mDevice + "): "
- + messageWhatToString(getCurrentMessage().what));
- sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs);
- broadcastConnectionState(
- mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTING);
- }
-
- @Override
- public void exit() {
- log("Exit Disconnecting(" + mDevice + "): "
- + messageWhatToString(getCurrentMessage().what));
- removeMessages(CONNECT_TIMEOUT);
- mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("Disconnecting process message(" + mDevice + "): "
- + messageWhatToString(message.what));
- switch (message.what) {
- case CONNECT:
- log("Disconnecting to " + mDevice);
- log("deferring this connection request " + mDevice);
- deferMessage(message);
- break;
- case DISCONNECT:
- Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice);
- break;
- case CONNECTION_STATE_CHANGED:
- int state = (int) message.obj;
- Log.w(TAG, "Disconnecting: connection state changed:" + state);
- if (state == BluetoothProfile.STATE_CONNECTED) {
- Log.e(TAG, "should never happen from this state");
- transitionTo(mConnected);
- } else {
- Log.w(TAG, "disconnection successful to " + mDevice);
- cancelActiveSync(null);
- transitionTo(mDisconnected);
- }
- break;
- case CONNECT_TIMEOUT:
- Log.w(TAG, "CONNECT_TIMEOUT");
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mDevice.equals(device)) {
- Log.e(TAG, "Unknown device timeout " + device);
- break;
- }
- transitionTo(mDisconnected);
- break;
- default:
- log("Disconnecting: not handled message:" + message.what);
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
log("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
if (fromState == BluetoothProfile.STATE_CONNECTED
@@ -1885,9 +1823,6 @@ public class BassClientStateMachine extends StateMachine {
case "Disconnected":
log("Disconnected");
return BluetoothProfile.STATE_DISCONNECTED;
- case "Disconnecting":
- log("Disconnecting");
- return BluetoothProfile.STATE_DISCONNECTING;
case "Connecting":
log("Connecting");
return BluetoothProfile.STATE_CONNECTING;
@@ -1949,22 +1884,6 @@ public class BassClientStateMachine extends StateMachine {
return Integer.toString(what);
}
- private static String profileStateToString(int state) {
- switch (state) {
- case BluetoothProfile.STATE_DISCONNECTED:
- return "DISCONNECTED";
- case BluetoothProfile.STATE_CONNECTING:
- return "CONNECTING";
- case BluetoothProfile.STATE_CONNECTED:
- return "CONNECTED";
- case BluetoothProfile.STATE_DISCONNECTING:
- return "DISCONNECTING";
- default:
- break;
- }
- return Integer.toString(state);
- }
-
/**
* Dump info
*/
@@ -2003,4 +1922,81 @@ public class BassClientStateMachine extends StateMachine {
}
Log.d(TAG, builder.toString());
}
+
+ /** Mockable wrapper of {@link BluetoothGatt}. */
+ @VisibleForTesting
+ public static class BluetoothGattTestableWrapper {
+ public final BluetoothGatt mWrappedBluetoothGatt;
+
+ BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt) {
+ mWrappedBluetoothGatt = bluetoothGatt;
+ }
+
+ /** See {@link BluetoothGatt#getServices()}. */
+ public List getServices() {
+ return mWrappedBluetoothGatt.getServices();
+ }
+
+ /** See {@link BluetoothGatt#getService(UUID)}. */
+ @Nullable
+ public BluetoothGattService getService(UUID uuid) {
+ return mWrappedBluetoothGatt.getService(uuid);
+ }
+
+ /** See {@link BluetoothGatt#discoverServices()}. */
+ public boolean discoverServices() {
+ return mWrappedBluetoothGatt.discoverServices();
+ }
+
+ /**
+ * See {@link BluetoothGatt#readCharacteristic(
+ * BluetoothGattCharacteristic)}.
+ */
+ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ return mWrappedBluetoothGatt.readCharacteristic(characteristic);
+ }
+
+ /**
+ * See {@link BluetoothGatt#writeCharacteristic(
+ * BluetoothGattCharacteristic, byte[], int)} .
+ */
+ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+ return mWrappedBluetoothGatt.writeCharacteristic(characteristic);
+ }
+
+ /** See {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */
+ public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+ return mWrappedBluetoothGatt.readDescriptor(descriptor);
+ }
+
+ /**
+ * See {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor,
+ * byte[])}.
+ */
+ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+ return mWrappedBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ /** See {@link BluetoothGatt#requestMtu(int)}. */
+ public boolean requestMtu(int mtu) {
+ return mWrappedBluetoothGatt.requestMtu(mtu);
+ }
+
+ /** See {@link BluetoothGatt#setCharacteristicNotification}. */
+ public boolean setCharacteristicNotification(
+ BluetoothGattCharacteristic characteristic, boolean enable) {
+ return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable);
+ }
+
+ /** See {@link BluetoothGatt#disconnect()}. */
+ public void disconnect() {
+ mWrappedBluetoothGatt.disconnect();
+ }
+
+ /** See {@link BluetoothGatt#close()}. */
+ public void close() {
+ mWrappedBluetoothGatt.close();
+ }
+ }
+
}
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassUtils.java b/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
index 42f88cad0cd414090e4e900bfecec99a566fd3ad..2850192012e073058b74228380f2fbd3cbaa4e30 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
@@ -141,4 +141,13 @@ class BassUtils {
log("array[" + i + "] :" + Byte.toUnsignedInt(array[i]));
}
}
+
+ static void reverse(byte[] address) {
+ int len = address.length;
+ for (int i = 0; i < len / 2; ++i) {
+ byte b = address[i];
+ address[i] = address[len - 1 - i];
+ address[len - 1 - i] = b;
+ }
+ }
}
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index 61fbb24988425f663c10b957f4e56031966f85d3..f043394a59ac762b3bca2aca68f9ecd48a9d328a 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
@@ -39,19 +40,22 @@ import android.os.Message;
import android.util.Log;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* The active device manager is responsible for keeping track of the
- * connected A2DP/HFP/AVRCP/HearingAid devices and select which device is
+ * connected A2DP/HFP/AVRCP/HearingAid/LE audio devices and select which device is
* active (for each profile).
+ * The active device manager selects a fallback device when the currently active device
+ * is disconnected, and it selects BT devices that are lastly activated one.
*
* Current policy (subject to change):
* 1) If the maximum number of connected devices is one, the manager doesn't
@@ -65,24 +69,24 @@ import java.util.Objects;
* - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP
* - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP
* - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid
+ * - BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED for LE audio
* If such broadcast is received (e.g., triggered indirectly by user
- * action on the UI), the device in the received broacast is marked
+ * action on the UI), the device in the received broadcast is marked
* as the current active device for that profile.
- * 5) If there is a HearingAid active device, then A2DP and HFP active devices
- * must be set to null (i.e., A2DP and HFP cannot have active devices).
- * The reason is because A2DP or HFP cannot be used together with HearingAid.
+ * 5) If there is a HearingAid active device, then A2DP, HFP and LE audio active devices
+ * must be set to null (i.e., A2DP, HFP and LE audio cannot have active devices).
+ * The reason is that A2DP, HFP or LE audio cannot be used together with HearingAid.
* 6) If there are no connected devices (e.g., during startup, or after all
* devices have been disconnected, the active device per profile
- * (A2DP/HFP/HearingAid) is selected as follows:
+ * (A2DP/HFP/HearingAid/LE audio) is selected as follows:
* 6.1) The last connected HearingAid device is selected as active.
- * If there is an active A2DP or HFP device, those must be set to null.
- * 6.2) The last connected A2DP or HFP device is selected as active.
+ * If there is an active A2DP, HFP or LE audio device, those must be set to null.
+ * 6.2) The last connected A2DP, HFP or LE audio device is selected as active.
* However, if there is an active HearingAid device, then the
- * A2DP or HFP active device is not set (must remain null).
+ * A2DP, HFP, or LE audio active device is not set (must remain null).
* 7) If the currently active device (per profile) is disconnected, the
* Active Device Manager just marks that the profile has no active device,
- * but does not attempt to select a new one. Currently, the expectation is
- * that the user will explicitly select the new active device.
+ * and the lastly activated BT device that is still connected would be selected.
* 8) If there is already an active device, and the corresponding
* ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device
* contained in the broadcast is marked as active. However, if
@@ -90,7 +94,7 @@ import java.util.Objects;
* as having no active device.
* 9) If a wired audio device is connected, the audio output is switched
* by the Audio Framework itself to that device. We detect this here,
- * and the active device for each profile (A2DP/HFP/HearingAid) is set
+ * and the active device for each profile (A2DP/HFP/HearingAid/LE audio) is set
* to null to reflect the output device state change. However, if the
* wired audio device is disconnected, we don't do anything explicit
* and apply the default behavior instead:
@@ -113,8 +117,12 @@ class ActiveDeviceManager {
private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 3;
private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 4;
private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 5;
- private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 6;
- private static final int MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED = 7;
+ private static final int MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE_CHANGED = 6;
+ private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 7;
+ private static final int MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE_CHANGED = 8;
+ private static final int MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED = 9;
+ private static final int MESSAGE_HAP_ACTION_CONNECTION_STATE_CHANGED = 10;
+ private static final int MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED = 11;
private final AdapterService mAdapterService;
private final ServiceFactory mFactory;
@@ -123,12 +131,17 @@ class ActiveDeviceManager {
private final AudioManager mAudioManager;
private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
- private final List mA2dpConnectedDevices = new LinkedList<>();
- private final List mHfpConnectedDevices = new LinkedList<>();
+ private final List mA2dpConnectedDevices = new ArrayList<>();
+ private final List mHfpConnectedDevices = new ArrayList<>();
+ private final List mHearingAidConnectedDevices = new ArrayList<>();
+ private final List mLeAudioConnectedDevices = new ArrayList<>();
+ private final List mLeHearingAidConnectedDevices = new ArrayList<>();
+ private List mPendingLeHearingAidActiveDevice = new ArrayList<>();
private BluetoothDevice mA2dpActiveDevice = null;
private BluetoothDevice mHfpActiveDevice = null;
private BluetoothDevice mHearingAidActiveDevice = null;
private BluetoothDevice mLeAudioActiveDevice = null;
+ private BluetoothDevice mLeHearingAidActiveDevice = null;
// Broadcast receiver for all changes
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -142,32 +155,48 @@ class ActiveDeviceManager {
switch (action) {
case BluetoothAdapter.ACTION_STATE_CHANGED:
mHandler.obtainMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED,
- intent).sendToTarget();
+ intent).sendToTarget();
break;
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
mHandler.obtainMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED,
- intent).sendToTarget();
+ intent).sendToTarget();
break;
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
mHandler.obtainMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED,
- intent).sendToTarget();
+ intent).sendToTarget();
break;
case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
mHandler.obtainMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED,
- intent).sendToTarget();
+ intent).sendToTarget();
break;
case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
mHandler.obtainMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED,
- intent).sendToTarget();
+ intent).sendToTarget();
+ break;
+ case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE_CHANGED,
+ intent).sendToTarget();
break;
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
mHandler.obtainMessage(MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED,
intent).sendToTarget();
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE_CHANGED,
+ intent).sendToTarget();
+ break;
case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
mHandler.obtainMessage(MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED,
intent).sendToTarget();
break;
+ case BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE_CHANGED,
+ intent).sendToTarget();
+ break;
+ case BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE:
+ mHandler.obtainMessage(MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED,
+ intent).sendToTarget();
+ break;
default:
Log.e(TAG, "Received unexpected intent, action=" + action);
break;
@@ -217,10 +246,10 @@ class ActiveDeviceManager {
break; // The device is already connected
}
mA2dpConnectedDevices.add(device);
- if (mHearingAidActiveDevice == null && mLeAudioActiveDevice == null) {
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
// New connected device: select it as active
setA2dpActiveDevice(device);
- break;
+ setLeAudioActiveDevice(null);
}
break;
}
@@ -233,7 +262,10 @@ class ActiveDeviceManager {
}
mA2dpConnectedDevices.remove(device);
if (Objects.equals(mA2dpActiveDevice, device)) {
- setA2dpActiveDevice(null);
+ if (mA2dpConnectedDevices.isEmpty()) {
+ setA2dpActiveDevice(null);
+ }
+ setFallbackDeviceActive();
}
}
}
@@ -251,6 +283,9 @@ class ActiveDeviceManager {
setHearingAidActiveDevice(null);
setLeAudioActiveDevice(null);
}
+ if (mHfpConnectedDevices.contains(device)) {
+ setHfpActiveDevice(device);
+ }
// Just assign locally the new value
mA2dpActiveDevice = device;
}
@@ -277,10 +312,10 @@ class ActiveDeviceManager {
break; // The device is already connected
}
mHfpConnectedDevices.add(device);
- if (mHearingAidActiveDevice == null && mLeAudioActiveDevice == null) {
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
// New connected device: select it as active
setHfpActiveDevice(device);
- break;
+ setLeAudioActiveDevice(null);
}
break;
}
@@ -293,7 +328,10 @@ class ActiveDeviceManager {
}
mHfpConnectedDevices.remove(device);
if (Objects.equals(mHfpActiveDevice, device)) {
- setHfpActiveDevice(null);
+ if (mHfpConnectedDevices.isEmpty()) {
+ setHfpActiveDevice(null);
+ }
+ setFallbackDeviceActive();
}
}
}
@@ -311,11 +349,58 @@ class ActiveDeviceManager {
setHearingAidActiveDevice(null);
setLeAudioActiveDevice(null);
}
+ if (mA2dpConnectedDevices.contains(device)) {
+ setA2dpActiveDevice(device);
+ }
// Just assign locally the new value
mHfpActiveDevice = device;
}
break;
+ case MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ if (prevState == nextState) {
+ // Nothing has changed
+ break;
+ }
+ if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ // Device connected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " connected");
+ }
+ if (mHearingAidConnectedDevices.contains(device)) {
+ break; // The device is already connected
+ }
+ mHearingAidConnectedDevices.add(device);
+ // New connected device: select it as active
+ setHearingAidActiveDevice(device);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ setLeAudioActiveDevice(null);
+ break;
+ }
+ if (prevState == BluetoothProfile.STATE_CONNECTED) {
+ // Device disconnected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " disconnected");
+ }
+ mHearingAidConnectedDevices.remove(device);
+ if (Objects.equals(mHearingAidActiveDevice, device)) {
+ if (mHearingAidConnectedDevices.isEmpty()) {
+ setHearingAidActiveDevice(null);
+ }
+ setFallbackDeviceActive();
+ }
+ }
+ }
+ break;
+
case MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED: {
Intent intent = (Intent) msg.obj;
BluetoothDevice device =
@@ -334,10 +419,65 @@ class ActiveDeviceManager {
}
break;
+ case MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ if (prevState == nextState) {
+ // Nothing has changed
+ break;
+ }
+ if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ // Device connected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " connected");
+ }
+ if (mLeAudioConnectedDevices.contains(device)) {
+ break; // The device is already connected
+ }
+ mLeAudioConnectedDevices.add(device);
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null
+ && mPendingLeHearingAidActiveDevice.isEmpty()) {
+ // New connected device: select it as active
+ setLeAudioActiveDevice(device);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
+ setLeHearingAidActiveDevice(device);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ }
+ break;
+ }
+ if (prevState == BluetoothProfile.STATE_CONNECTED) {
+ // Device disconnected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " disconnected");
+ }
+ mLeAudioConnectedDevices.remove(device);
+ mLeHearingAidConnectedDevices.remove(device);
+ if (Objects.equals(mLeAudioActiveDevice, device)) {
+ if (mLeAudioConnectedDevices.isEmpty()) {
+ setLeAudioActiveDevice(null);
+ }
+ setFallbackDeviceActive();
+ }
+ }
+ }
+ break;
+
case MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED: {
Intent intent = (Intent) msg.obj;
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device != null && !mLeAudioConnectedDevices.contains(device)) {
+ mLeAudioConnectedDevices.add(device);
+ }
if (DBG) {
Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED): "
+ "device= " + device);
@@ -351,6 +491,75 @@ class ActiveDeviceManager {
mLeAudioActiveDevice = device;
}
break;
+
+ case MESSAGE_HAP_ACTION_CONNECTION_STATE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ if (prevState == nextState) {
+ // Nothing has changed
+ break;
+ }
+ if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ // Device connected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " connected");
+ }
+ if (mLeHearingAidConnectedDevices.contains(device)) {
+ break; // The device is already connected
+ }
+ mLeHearingAidConnectedDevices.add(device);
+ if (!mLeAudioConnectedDevices.contains(device)) {
+ mPendingLeHearingAidActiveDevice.add(device);
+ } else if (Objects.equals(mLeAudioActiveDevice, device)) {
+ mLeHearingAidActiveDevice = device;
+ } else {
+ // New connected device: select it as active
+ setLeHearingAidActiveDevice(device);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ }
+ break;
+ }
+ if (prevState == BluetoothProfile.STATE_CONNECTED) {
+ // Device disconnected
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE"
+ + "_CHANGED): device " + device + " disconnected");
+ }
+ mLeHearingAidConnectedDevices.remove(device);
+ mPendingLeHearingAidActiveDevice.remove(device);
+ if (Objects.equals(mLeHearingAidActiveDevice, device)) {
+ mLeHearingAidActiveDevice = null;
+ }
+ }
+ }
+ break;
+
+ case MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device != null && !mLeHearingAidConnectedDevices.contains(device)) {
+ mLeHearingAidConnectedDevices.add(device);
+ }
+ if (DBG) {
+ Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED): "
+ + "device= " + device);
+ }
+ // Just assign locally the new value
+ if (device != null && !Objects.equals(mLeHearingAidActiveDevice, device)) {
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ setHearingAidActiveDevice(null);
+ }
+ mLeHearingAidActiveDevice = mLeAudioActiveDevice = device;
+ }
+ break;
}
}
}
@@ -418,8 +627,12 @@ class ActiveDeviceManager {
filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE);
mAdapterService.registerReceiver(mReceiver, filter);
mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
@@ -508,6 +721,133 @@ class ActiveDeviceManager {
return;
}
mLeAudioActiveDevice = device;
+ if (device == null) {
+ mLeHearingAidActiveDevice = null;
+ mPendingLeHearingAidActiveDevice.remove(device);
+ }
+ }
+
+ private void setLeHearingAidActiveDevice(BluetoothDevice device) {
+ if (!Objects.equals(mLeAudioActiveDevice, device)) {
+ setLeAudioActiveDevice(device);
+ }
+ if (Objects.equals(mLeAudioActiveDevice, device)) {
+ // setLeAudioActiveDevice succeed
+ mLeHearingAidActiveDevice = device;
+ mPendingLeHearingAidActiveDevice.remove(device);
+ }
+ }
+
+ private void setFallbackDeviceActive() {
+ if (DBG) {
+ Log.d(TAG, "setFallbackDeviceActive");
+ }
+ DatabaseManager dbManager = mAdapterService.getDatabase();
+ if (dbManager == null) {
+ return;
+ }
+ List connectedHearingAidDevices = new ArrayList<>();
+ if (!mHearingAidConnectedDevices.isEmpty()) {
+ connectedHearingAidDevices.addAll(mHearingAidConnectedDevices);
+ }
+ if (!mLeHearingAidConnectedDevices.isEmpty()) {
+ connectedHearingAidDevices.addAll(mLeHearingAidConnectedDevices);
+ }
+ if (!connectedHearingAidDevices.isEmpty()) {
+ BluetoothDevice device =
+ dbManager.getMostRecentlyConnectedDevicesInList(connectedHearingAidDevices);
+ if (device != null) {
+ if (mHearingAidConnectedDevices.contains(device)) {
+ if (DBG) {
+ Log.d(TAG, "set hearing aid device active: " + device);
+ }
+ setHearingAidActiveDevice(device);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ setLeAudioActiveDevice(null);
+ } else {
+ if (DBG) {
+ Log.d(TAG, "set LE hearing aid device active: " + device);
+ }
+ setLeHearingAidActiveDevice(device);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ }
+ return;
+ }
+ }
+
+ A2dpService a2dpService = mFactory.getA2dpService();
+ BluetoothDevice a2dpFallbackDevice = null;
+ if (a2dpService != null) {
+ a2dpFallbackDevice = a2dpService.getFallbackDevice();
+ }
+
+ HeadsetService headsetService = mFactory.getHeadsetService();
+ BluetoothDevice headsetFallbackDevice = null;
+ if (headsetService != null) {
+ headsetFallbackDevice = headsetService.getFallbackDevice();
+ }
+
+ List connectedDevices = new ArrayList<>();
+ connectedDevices.addAll(mLeAudioConnectedDevices);
+ switch (mAudioManager.getMode()) {
+ case AudioManager.MODE_NORMAL:
+ if (a2dpFallbackDevice != null) {
+ connectedDevices.add(a2dpFallbackDevice);
+ }
+ break;
+ case AudioManager.MODE_RINGTONE:
+ if (headsetFallbackDevice != null && headsetService.isInbandRingingEnabled()) {
+ connectedDevices.add(headsetFallbackDevice);
+ }
+ break;
+ default:
+ if (headsetFallbackDevice != null) {
+ connectedDevices.add(headsetFallbackDevice);
+ }
+ }
+ BluetoothDevice device = dbManager.getMostRecentlyConnectedDevicesInList(connectedDevices);
+ if (device != null) {
+ if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) {
+ if (Objects.equals(a2dpFallbackDevice, device)) {
+ if (DBG) {
+ Log.d(TAG, "set A2DP device active: " + device);
+ }
+ setA2dpActiveDevice(device);
+ if (headsetFallbackDevice != null) {
+ setHfpActiveDevice(device);
+ setLeAudioActiveDevice(null);
+ }
+ } else {
+ if (DBG) {
+ Log.d(TAG, "set LE audio device active: " + device);
+ }
+ setLeAudioActiveDevice(device);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ }
+ } else {
+ if (Objects.equals(headsetFallbackDevice, device)) {
+ if (DBG) {
+ Log.d(TAG, "set HFP device active: " + device);
+ }
+ setHfpActiveDevice(device);
+ if (a2dpFallbackDevice != null) {
+ setA2dpActiveDevice(a2dpFallbackDevice);
+ setLeAudioActiveDevice(null);
+ }
+ } else {
+ if (DBG) {
+ Log.d(TAG, "set LE audio device active: " + device);
+ }
+ setLeAudioActiveDevice(device);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ }
+ }
+ }
}
private void resetState() {
@@ -517,8 +857,15 @@ class ActiveDeviceManager {
mHfpConnectedDevices.clear();
mHfpActiveDevice = null;
+ mHearingAidConnectedDevices.clear();
mHearingAidActiveDevice = null;
+
+ mLeAudioConnectedDevices.clear();
mLeAudioActiveDevice = null;
+
+ mLeHearingAidConnectedDevices.clear();
+ mLeHearingAidActiveDevice = null;
+ mPendingLeHearingAidActiveDevice.clear();
}
@VisibleForTesting
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterApp.java b/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
index 0c22c7cb5aa38db7c119d029a33e414f533ad394..99e3f4fc4cec6a28bb5b845e215a725c34d0d763 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
@@ -52,6 +52,11 @@ public class AdapterApp extends Application {
if (DBG) {
Log.d(TAG, "onCreate");
}
+ try {
+ DataMigration.run(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Migration failure: ", e);
+ }
Config.init(this);
}
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 2ea49ee9ec1286c4e73d149abcbcb15d0f0d914b..c5fb78b1e6f396d83da8934df0352a3a9096364e 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -21,7 +21,6 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser;
-import static com.android.bluetooth.Utils.callerIsSystemOrActiveUser;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import static com.android.bluetooth.Utils.enforceCdmAssociation;
import static com.android.bluetooth.Utils.enforceDumpPermission;
@@ -169,6 +168,7 @@ public class AdapterService extends Service {
private static final int MIN_OFFLOADED_FILTERS = 10;
private static final int MIN_OFFLOADED_SCAN_STORAGE_BYTES = 1024;
private static final Duration PENDING_SOCKET_HANDOFF_TIMEOUT = Duration.ofMinutes(1);
+ private static final Duration GENERATE_LOCAL_OOB_DATA_TIMEOUT = Duration.ofSeconds(2);
private final Object mEnergyInfoLock = new Object();
private int mStackReportedState;
@@ -204,11 +204,11 @@ public class AdapterService extends Service {
static final String LOCAL_MAC_ADDRESS_PERM = android.Manifest.permission.LOCAL_MAC_ADDRESS;
static final String RECEIVE_MAP_PERM = android.Manifest.permission.RECEIVE_BLUETOOTH_MAP;
- private static final String PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE =
+ static final String PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE =
"phonebook_access_permission";
- private static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
+ static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
"message_access_permission";
- private static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE = "sim_access_permission";
+ static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE = "sim_access_permission";
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
@@ -262,7 +262,8 @@ public class AdapterService extends Service {
}
private BluetoothAdapter mAdapter;
- private AdapterProperties mAdapterProperties;
+ @VisibleForTesting
+ AdapterProperties mAdapterProperties;
private AdapterState mAdapterStateMachine;
private BondStateMachine mBondStateMachine;
private JniCallbacks mJniCallbacks;
@@ -749,8 +750,9 @@ public class AdapterService extends Service {
nonSupportedProfiles.add(BassClientService.class);
}
- if (isLeAudioBroadcastSourceSupported()) {
- Config.addSupportedProfile(BluetoothProfile.LE_AUDIO_BROADCAST);
+ if (!isLeAudioBroadcastSourceSupported()) {
+ Config.updateSupportedProfileMask(
+ false, LeAudioService.class, BluetoothProfile.LE_AUDIO_BROADCAST);
}
if (!nonSupportedProfiles.isEmpty()) {
@@ -1360,7 +1362,8 @@ public class AdapterService extends Service {
}
@BluetoothAdapter.RfcommListenerResult
- private int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) {
+ @VisibleForTesting
+ int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) {
RfcommListenerData listenerData = mBluetoothServerSockets.get(uuid.getUuid());
if (listenerData == null) {
@@ -1379,7 +1382,8 @@ public class AdapterService extends Service {
return listenerData.closeServerAndPendingSockets(mHandler);
}
- private IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord(
+ @VisibleForTesting
+ IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord(
ParcelUuid uuid, AttributionSource attributionSource) {
IncomingRfcommSocketInfo socketInfo = new IncomingRfcommSocketInfo();
@@ -1547,7 +1551,8 @@ public class AdapterService extends Service {
}
}
- private boolean isAvailable() {
+ @VisibleForTesting
+ boolean isAvailable() {
return !mCleaningUp;
}
@@ -1617,7 +1622,7 @@ public class AdapterService extends Service {
}
private boolean enable(boolean quietMode, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "enable")
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "enable")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService enable")) {
return false;
@@ -1636,7 +1641,7 @@ public class AdapterService extends Service {
}
private boolean disable(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "disable")
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "disable")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService disable")) {
return false;
@@ -1685,7 +1690,7 @@ public class AdapterService extends Service {
}
private List getUuids(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getUuids")
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getUuids")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getUuids")) {
return new ArrayList<>();
@@ -1708,7 +1713,8 @@ public class AdapterService extends Service {
}
public String getIdentityAddress(String address) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getIdentityAddress")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getIdentityAddress")
|| !Utils.checkConnectPermissionForDataDelivery(
service, Utils.getCallingAttributionSource(mService),
"AdapterService getIdentityAddress")) {
@@ -1728,7 +1734,7 @@ public class AdapterService extends Service {
}
private String getName(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getName")
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getName")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getName")) {
return null;
@@ -1748,7 +1754,9 @@ public class AdapterService extends Service {
}
private int getNameLengthForAdvertise(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getNameLengthForAdvertise")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "getNameLengthForAdvertise")
|| !Utils.checkAdvertisePermissionForDataDelivery(
service, attributionSource, TAG)) {
return -1;
@@ -1768,7 +1776,7 @@ public class AdapterService extends Service {
}
private boolean setName(String name, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "setName")
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setName")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService setName")) {
return false;
@@ -1788,7 +1796,8 @@ public class AdapterService extends Service {
}
private BluetoothClass getBluetoothClass(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getBluetoothClass")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getBluetoothClass")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterSource getBluetoothClass")) {
return null;
@@ -1809,7 +1818,7 @@ public class AdapterService extends Service {
private boolean setBluetoothClass(BluetoothClass bluetoothClass, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setBluetoothClass")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -1836,7 +1845,8 @@ public class AdapterService extends Service {
}
private int getIoCapability(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getIoCapability")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getIoCapability")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getIoCapability")) {
return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1857,7 +1867,7 @@ public class AdapterService extends Service {
private boolean setIoCapability(int capability, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setIoCapability")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -1882,7 +1892,8 @@ public class AdapterService extends Service {
}
private int getLeIoCapability(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getLeIoCapability")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getLeIoCapability")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getLeIoCapability")) {
return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1903,7 +1914,7 @@ public class AdapterService extends Service {
private boolean setLeIoCapability(int capability, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setLeIoCapability")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -1948,7 +1959,8 @@ public class AdapterService extends Service {
}
private int setScanMode(int mode, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "setScanMode")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setScanMode")
|| !Utils.checkScanPermissionForDataDelivery(
service, attributionSource, "AdapterService setScanMode")) {
return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION;
@@ -1970,7 +1982,8 @@ public class AdapterService extends Service {
}
private long getDiscoverableTimeout(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoverableTimeout")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getDiscoverableTimeout")
|| !Utils.checkScanPermissionForDataDelivery(
service, attributionSource, "AdapterService getDiscoverableTimeout")) {
return -1;
@@ -1990,7 +2003,8 @@ public class AdapterService extends Service {
}
private int setDiscoverableTimeout(long timeout, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "setDiscoverableTimeout")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setDiscoverableTimeout")
|| !Utils.checkScanPermissionForDataDelivery(
service, attributionSource, "AdapterService setDiscoverableTimeout")) {
return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION;
@@ -2011,7 +2025,8 @@ public class AdapterService extends Service {
}
private boolean startDiscovery(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "startDiscovery")) {
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "startDiscovery")) {
return false;
}
@@ -2033,7 +2048,8 @@ public class AdapterService extends Service {
}
private boolean cancelDiscovery(AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelDiscovery")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "cancelDiscovery")
|| !Utils.checkScanPermissionForDataDelivery(
service, attributionSource, "AdapterService cancelDiscovery")) {
return false;
@@ -2075,7 +2091,7 @@ public class AdapterService extends Service {
private long getDiscoveryEndMillis(AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getDiscoveryEndMillis")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return -1;
}
@@ -2209,7 +2225,8 @@ public class AdapterService extends Service {
private boolean cancelBondProcess(
BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelBondProcess")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "cancelBondProcess")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService cancelBondProcess")) {
return false;
@@ -2236,7 +2253,8 @@ public class AdapterService extends Service {
}
private boolean removeBond(BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "removeBond")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "removeBond")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService removeBond")) {
return false;
@@ -2311,7 +2329,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveOrManagedUser(service, TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "generateLocalOobData")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return;
}
@@ -2402,7 +2420,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "removeActiveDevice")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2422,7 +2440,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setActiveDevice")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2445,7 +2463,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getActiveDevices")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return new ArrayList<>();
}
@@ -2470,7 +2488,7 @@ public class AdapterService extends Service {
if (service == null) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
}
- if (!callerIsSystemOrActiveUser(TAG, "connectAllEnabledProfiles")) {
+ if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "connectAllEnabledProfiles")) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
}
if (device == null) {
@@ -2509,7 +2527,8 @@ public class AdapterService extends Service {
if (service == null) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
}
- if (!callerIsSystemOrActiveUser(TAG, "disconnectAllEnabledProfiles")) {
+ if (!callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "disconnectAllEnabledProfiles")) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
}
if (device == null) {
@@ -2624,7 +2643,7 @@ public class AdapterService extends Service {
if (service == null) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
}
- if (!callerIsSystemOrActiveUser(TAG, "setRemoteAlias")) {
+ if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "setRemoteAlias")) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
}
if (name != null && name.isEmpty()) {
@@ -2743,7 +2762,8 @@ public class AdapterService extends Service {
private boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode,
AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "setPin")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setPin")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService setPin")) {
return false;
@@ -2778,7 +2798,8 @@ public class AdapterService extends Service {
private boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey,
AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "setPasskey")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setPasskey")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService setPasskey")) {
return false;
@@ -2814,7 +2835,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setPairingConfirmation")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2845,7 +2866,7 @@ public class AdapterService extends Service {
private boolean getSilenceMode(BluetoothDevice device, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getSilenceMode")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2868,7 +2889,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setSilenceMode")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2891,7 +2912,9 @@ public class AdapterService extends Service {
private int getPhonebookAccessPermission(
BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getPhonebookAccessPermission")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(
+ service, TAG, "getPhonebookAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getPhonebookAccessPermission")) {
return BluetoothDevice.ACCESS_UNKNOWN;
@@ -2913,7 +2936,8 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "setPhonebookAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2936,7 +2960,9 @@ public class AdapterService extends Service {
private int getMessageAccessPermission(
BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getMessageAccessPermission")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "getMessageAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getMessageAccessPermission")) {
return BluetoothDevice.ACCESS_UNKNOWN;
@@ -2958,7 +2984,8 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "setMessageAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -2981,7 +3008,9 @@ public class AdapterService extends Service {
private int getSimAccessPermission(
BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getSimAccessPermission")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "getSimAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getSimAccessPermission")) {
return BluetoothDevice.ACCESS_UNKNOWN;
@@ -3003,7 +3032,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setSimAccessPermission")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3036,7 +3065,8 @@ public class AdapterService extends Service {
private boolean sdpSearch(
BluetoothDevice device, ParcelUuid uuid, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "sdpSearch")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "sdpSearch")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService sdpSearch")) {
return false;
@@ -3060,7 +3090,8 @@ public class AdapterService extends Service {
}
private int getBatteryLevel(BluetoothDevice device, AttributionSource attributionSource) {
AdapterService service = getService();
- if (service == null || !callerIsSystemOrActiveUser(TAG, "getBatteryLevel")
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getBatteryLevel")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getBatteryLevel")) {
return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
@@ -3156,7 +3187,8 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "registerBluetoothConnectionCallback")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3178,7 +3210,8 @@ public class AdapterService extends Service {
IBluetoothConnectionCallback callback, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "unregisterBluetoothConnectionCallback")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3200,7 +3233,7 @@ public class AdapterService extends Service {
void registerCallback(IBluetoothCallback callback, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "registerCallback")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return;
}
@@ -3224,7 +3257,7 @@ public class AdapterService extends Service {
void unregisterCallback(IBluetoothCallback callback, AttributionSource source) {
AdapterService service = getService();
if (service == null || service.mCallbacks == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "unregisterCallback")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return;
}
@@ -3399,7 +3432,8 @@ public class AdapterService extends Service {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
}
- if (service.isLeAudioBroadcastSourceSupported()) {
+ long supportBitMask = Config.getSupportedProfilesBitMask();
+ if ((supportBitMask & (1 << BluetoothProfile.LE_AUDIO_BROADCAST)) != 0) {
return BluetoothStatusCodes.FEATURE_SUPPORTED;
}
@@ -3499,7 +3533,8 @@ public class AdapterService extends Service {
BluetoothDevice device, AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "registerMetadataListener")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3534,7 +3569,8 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "unregisterMetadataListener")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3563,7 +3599,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setMetadata")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return false;
}
@@ -3589,7 +3625,7 @@ public class AdapterService extends Service {
AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getMetadata")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return null;
}
@@ -3623,7 +3659,7 @@ public class AdapterService extends Service {
void onLeServiceUp(AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "onLeServiceUp")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return;
}
@@ -3646,7 +3682,7 @@ public class AdapterService extends Service {
void onBrEdrDown(AttributionSource source) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "onBrEdrDown")
|| !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
return;
}
@@ -3686,7 +3722,7 @@ public class AdapterService extends Service {
private boolean allowLowLatencyAudio(boolean allowed, BluetoothDevice device) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "allowLowLatencyAudio")
|| !Utils.checkConnectPermissionForDataDelivery(
service, Utils.getCallingAttributionSource(service),
"AdapterService allowLowLatencyAudio")) {
@@ -3716,7 +3752,7 @@ public class AdapterService extends Service {
AttributionSource attributionSource) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "startRfcommListener")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService startRfcommListener")) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
@@ -3741,7 +3777,7 @@ public class AdapterService extends Service {
private int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG, "stopRfcommListener")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService stopRfcommListener")) {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
@@ -3767,7 +3803,8 @@ public class AdapterService extends Service {
ParcelUuid uuid, AttributionSource attributionSource) {
AdapterService service = getService();
if (service == null
- || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "retrievePendingSocketForServiceRecord")
|| !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource,
"AdapterService retrievePendingSocketForServiceRecord")) {
@@ -3831,7 +3868,8 @@ public class AdapterService extends Service {
return mAdapterProperties.getName().length();
}
- private static boolean isValidIoCapability(int capability) {
+ @VisibleForTesting
+ static boolean isValidIoCapability(int capability) {
if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
Log.e(TAG, "Invalid IO capability value - " + capability);
return false;
@@ -3999,15 +4037,31 @@ public class AdapterService extends Service {
if (mOobDataCallbackQueue.peek() != null) {
try {
callback.onError(BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_OOB_REQUEST);
- return;
} catch (RemoteException e) {
Log.e(TAG, "Failed to make callback", e);
}
+ return;
}
mOobDataCallbackQueue.offer(callback);
+ mHandler.postDelayed(() -> removeFromOobDataCallbackQueue(callback),
+ GENERATE_LOCAL_OOB_DATA_TIMEOUT.toMillis());
generateLocalOobDataNative(transport);
}
+ private synchronized void removeFromOobDataCallbackQueue(IBluetoothOobDataCallback callback) {
+ if (callback == null) {
+ return;
+ }
+
+ if (mOobDataCallbackQueue.peek() == callback) {
+ try {
+ mOobDataCallbackQueue.poll().onError(BluetoothStatusCodes.ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to make OobDataCallback to remove callback from queue", e);
+ }
+ }
+ }
+
/* package */ synchronized void notifyOobDataCallback(int transport, OobData oobData) {
if (mOobDataCallbackQueue.peek() == null) {
Log.e(TAG, "Failed to make callback, no callback exists");
@@ -4215,8 +4269,8 @@ public class AdapterService extends Service {
Log.e(TAG, "getActiveDevices: LeAudioService is null");
} else {
activeDevices = mLeAudioService.getActiveDevices();
- Log.i(TAG, "getActiveDevices: LeAudio devices: Out["
- + activeDevices.get(0) + "] - In[" + activeDevices.get(1) + "]");
+ Log.i(TAG, "getActiveDevices: LeAudio devices: Lead["
+ + activeDevices.get(0) + "] - member_1[" + activeDevices.get(1) + "]");
}
break;
default:
@@ -4369,95 +4423,131 @@ public class AdapterService extends Service {
return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
}
- if (mA2dpService != null && mA2dpService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mA2dpService != null && (mA2dpService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mA2dpService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp");
mA2dpService.disconnect(device);
}
- if (mA2dpSinkService != null && mA2dpSinkService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mA2dpSinkService != null && (mA2dpSinkService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mA2dpSinkService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp Sink");
mA2dpSinkService.disconnect(device);
}
- if (mHeadsetService != null && mHeadsetService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHeadsetService != null && (mHeadsetService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHeadsetService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG,
"disconnectAllEnabledProfiles: Disconnecting Headset Profile");
mHeadsetService.disconnect(device);
}
- if (mHeadsetClientService != null && mHeadsetClientService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHeadsetClientService != null && (mHeadsetClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHeadsetClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting HFP");
mHeadsetClientService.disconnect(device);
}
- if (mMapClientService != null && mMapClientService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mMapClientService != null && (mMapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mMapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP Client");
mMapClientService.disconnect(device);
}
- if (mMapService != null && mMapService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mMapService != null && (mMapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mMapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
mMapService.disconnect(device);
}
- if (mHidDeviceService != null && mHidDeviceService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHidDeviceService != null && (mHidDeviceService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHidDeviceService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Device Profile");
mHidDeviceService.disconnect(device);
}
- if (mHidHostService != null && mHidHostService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHidHostService != null && (mHidHostService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHidHostService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
mHidHostService.disconnect(device);
}
- if (mPanService != null && mPanService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mPanService != null && (mPanService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mPanService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pan Profile");
mPanService.disconnect(device);
}
- if (mPbapClientService != null && mPbapClientService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mPbapClientService != null && (mPbapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mPbapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Client");
mPbapClientService.disconnect(device);
}
- if (mPbapService != null && mPbapService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mPbapService != null && (mPbapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mPbapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Server");
mPbapService.disconnect(device);
}
- if (mHearingAidService != null && mHearingAidService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHearingAidService != null && (mHearingAidService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHearingAidService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
mHearingAidService.disconnect(device);
}
- if (mHapClientService != null && mHapClientService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mHapClientService != null && (mHapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mHapClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hearing Access Profile Client");
mHapClientService.disconnect(device);
}
- if (mVolumeControlService != null && mVolumeControlService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mVolumeControlService != null && (mVolumeControlService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mVolumeControlService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Volume Control Profile");
mVolumeControlService.disconnect(device);
}
- if (mSapService != null && mSapService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mSapService != null && (mSapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mSapService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Sap Profile");
mSapService.disconnect(device);
}
if (mCsipSetCoordinatorService != null
- && mCsipSetCoordinatorService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ && (mCsipSetCoordinatorService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mCsipSetCoordinatorService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Coordinater Set Profile");
mCsipSetCoordinatorService.disconnect(device);
}
- if (mLeAudioService != null && mLeAudioService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mLeAudioService != null && (mLeAudioService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mLeAudioService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting LeAudio profile (BAP)");
mLeAudioService.disconnect(device);
}
- if (mBassClientService != null && mBassClientService.getConnectionState(device)
- == BluetoothProfile.STATE_CONNECTED) {
+ if (mBassClientService != null && (mBassClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTED
+ || mBassClientService.getConnectionState(device)
+ == BluetoothProfile.STATE_CONNECTING)) {
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting "
+ "LE Broadcast Assistant Profile");
mBassClientService.disconnect(device);
@@ -4560,6 +4650,8 @@ public class AdapterService extends Service {
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS;
case /*HCI_ERR_PEER_USER*/ 0x13:
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST;
+ case /*HCI_ERR_REMOTE_POWER_OFF*/ 0x15:
+ return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST;
case /*HCI_ERR_CONN_CAUSE_LOCAL_HOST*/ 0x16:
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST;
case /*HCI_ERR_UNSUPPORTED_REM_FEATURE*/ 0x1A:
@@ -4656,8 +4748,7 @@ public class AdapterService extends Service {
* @return true, if the LE audio broadcast source is supported
*/
public boolean isLeAudioBroadcastSourceSupported() {
- return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false)
- && mAdapterProperties.isLePeriodicAdvertisingSupported()
+ return mAdapterProperties.isLePeriodicAdvertisingSupported()
&& mAdapterProperties.isLeExtendedAdvertisingSupported()
&& mAdapterProperties.isLeIsochronousBroadcasterSupported();
}
@@ -4674,6 +4765,10 @@ public class AdapterService extends Service {
|| mAdapterProperties.isLePeriodicAdvertisingSyncTransferRecipientSupported());
}
+ public long getSupportedProfilesBitMask() {
+ return Config.getSupportedProfilesBitMask();
+ }
+
/**
* Check if the LE audio CIS central feature is supported.
*
@@ -4705,7 +4800,8 @@ public class AdapterService extends Service {
return mAdapterProperties.isA2dpOffloadEnabled();
}
- private BluetoothActivityEnergyInfo reportActivityInfo() {
+ @VisibleForTesting
+ BluetoothActivityEnergyInfo reportActivityInfo() {
if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON
|| !mAdapterProperties.isActivityAndEnergyReportingSupported()) {
return null;
@@ -4921,6 +5017,12 @@ public class AdapterService extends Service {
@VisibleForTesting
public void metadataChanged(String address, int key, byte[] value) {
BluetoothDevice device = mRemoteDevices.getDevice(Utils.getBytesFromAddress(address));
+
+ // pass just interesting metadata to native, to reduce spam
+ if (key == BluetoothDevice.METADATA_LE_AUDIO) {
+ metadataChangedNative(Utils.getBytesFromAddress(address), key, value);
+ }
+
if (mMetadataListeners.containsKey(device)) {
ArrayList list = mMetadataListeners.get(device);
for (IBluetoothMetadataListener listener : list) {
@@ -5069,96 +5171,16 @@ public class AdapterService extends Service {
}
}
- // Boolean flags
- private static final String GD_CORE_FLAG = "INIT_gd_core";
- private static final String GD_ADVERTISING_FLAG = "INIT_gd_advertising";
- private static final String GD_SCANNING_FLAG = "INIT_gd_scanning";
- private static final String GD_HCI_FLAG = "INIT_gd_hci";
- private static final String GD_CONTROLLER_FLAG = "INIT_gd_controller";
- private static final String GD_ACL_FLAG = "INIT_gd_acl";
- private static final String GD_L2CAP_FLAG = "INIT_gd_l2cap";
- private static final String GD_RUST_FLAG = "INIT_gd_rust";
- private static final String GD_LINK_POLICY_FLAG = "INIT_gd_link_policy";
- private static final String GATT_ROBUST_CACHING_CLIENT_FLAG = "INIT_gatt_robust_caching_client";
- private static final String GATT_ROBUST_CACHING_SERVER_FLAG = "INIT_gatt_robust_caching_server";
-
- /**
- * Logging flags logic (only applies to DEBUG and VERBOSE levels):
- * if LOG_TAG in LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG:
- * DO NOT LOG
- * else if LOG_TAG in LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG:
- * DO LOG
- * else if LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG:
- * DO LOG
- * else:
- * DO NOT LOG
- */
- private static final String LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG =
- "INIT_logging_debug_enabled_for_all";
- // String flags
- // Comma separated tags
- private static final String LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG =
- "INIT_logging_debug_enabled_for_tags";
- private static final String LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG =
- "INIT_logging_debug_disabled_for_tags";
- private static final String BTAA_HCI_LOG_FLAG = "INIT_btaa_hci";
-
+ @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
private String[] getInitFlags() {
+ final DeviceConfig.Properties properties =
+ DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BLUETOOTH);
ArrayList initFlags = new ArrayList<>();
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_CORE_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_CORE_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_ADVERTISING_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_ADVERTISING_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_SCANNING_FLAG,
- Config.isGdEnabledUpToScanningLayer())) {
- initFlags.add(String.format("%s=%s", GD_SCANNING_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_HCI_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_HCI_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_CONTROLLER_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_CONTROLLER_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_ACL_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_ACL_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_L2CAP_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_L2CAP_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_RUST_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_RUST_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_LINK_POLICY_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GD_LINK_POLICY_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
- GATT_ROBUST_CACHING_CLIENT_FLAG, true)) {
- initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_CLIENT_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
- GATT_ROBUST_CACHING_SERVER_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_SERVER_FLAG, "true"));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
- LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, false)) {
- initFlags.add(String.format("%s=%s", LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, "true"));
- }
- String debugLoggingEnabledTags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BLUETOOTH,
- LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG, "");
- if (!debugLoggingEnabledTags.isEmpty()) {
- initFlags.add(String.format("%s=%s", LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG,
- debugLoggingEnabledTags));
- }
- String debugLoggingDisabledTags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BLUETOOTH,
- LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG, "");
- if (!debugLoggingDisabledTags.isEmpty()) {
- initFlags.add(String.format("%s=%s", LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG,
- debugLoggingDisabledTags));
- }
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, BTAA_HCI_LOG_FLAG, true)) {
- initFlags.add(String.format("%s=%s", BTAA_HCI_LOG_FLAG, "true"));
+ for (String property: properties.getKeyset()) {
+ if (property.startsWith("INIT_")) {
+ initFlags.add(String.format("%s=%s", property,
+ properties.getString(property, null)));
+ }
}
return initFlags.toArray(new String[0]);
}
@@ -5226,33 +5248,30 @@ public class AdapterService extends Service {
}
}
- public static int getScanQuotaCount() {
- if (sAdapterService == null) {
- return DeviceConfigListener.DEFAULT_SCAN_QUOTA_COUNT;
- }
-
- synchronized (sAdapterService.mDeviceConfigLock) {
- return sAdapterService.mScanQuotaCount;
+ /**
+ * Returns scan quota count.
+ */
+ public int getScanQuotaCount() {
+ synchronized (mDeviceConfigLock) {
+ return mScanQuotaCount;
}
}
- public static long getScanQuotaWindowMillis() {
- if (sAdapterService == null) {
- return DeviceConfigListener.DEFAULT_SCAN_QUOTA_WINDOW_MILLIS;
- }
-
- synchronized (sAdapterService.mDeviceConfigLock) {
- return sAdapterService.mScanQuotaWindowMillis;
+ /**
+ * Returns scan quota window in millis.
+ */
+ public long getScanQuotaWindowMillis() {
+ synchronized (mDeviceConfigLock) {
+ return mScanQuotaWindowMillis;
}
}
- public static long getScanTimeoutMillis() {
- if (sAdapterService == null) {
- return DeviceConfigListener.DEFAULT_SCAN_TIMEOUT_MILLIS;
- }
-
- synchronized (sAdapterService.mDeviceConfigLock) {
- return sAdapterService.mScanTimeoutMillis;
+ /**
+ * Returns scan timeout in millis.
+ */
+ public long getScanTimeoutMillis() {
+ synchronized (mDeviceConfigLock) {
+ return mScanTimeoutMillis;
}
}
@@ -5552,6 +5571,8 @@ public class AdapterService extends Service {
private native boolean allowLowLatencyAudioNative(boolean allowed, byte[] address);
+ private native void metadataChangedNative(byte[] address, int key, byte[] value);
+
// Returns if this is a mock object. This is currently used in testing so that we may not call
// System.exit() while finalizing the object. Otherwise GC of mock objects unfortunately ends up
// calling finalize() which in turn calls System.exit() and the process crashes.
diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothAdapterProxy.java b/android/app/src/com/android/bluetooth/btservice/BluetoothAdapterProxy.java
new file mode 100644
index 0000000000000000000000000000000000000000..4376097c22df20d333ac8bf5415389340c6a0ec9
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/BluetoothAdapterProxy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 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.btservice;
+
+import android.bluetooth.BluetoothAdapter;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A proxy class that facilitates testing of the ScanManager.
+ *
+ * This is necessary due to the "final" attribute of the BluetoothAdapter class. In order to
+ * test the correct functioning of the ScanManager class, the final class must be put
+ * into a container that can be mocked correctly.
+ */
+public class BluetoothAdapterProxy {
+ private static final String TAG = BluetoothAdapterProxy.class.getSimpleName();
+ private static BluetoothAdapterProxy sInstance;
+ private static final Object INSTANCE_LOCK = new Object();
+
+ private BluetoothAdapterProxy() {}
+
+ /**
+ * Get the singleton instance of proxy.
+ *
+ * @return the singleton instance, guaranteed not null
+ */
+ public static BluetoothAdapterProxy getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sInstance == null) {
+ sInstance = new BluetoothAdapterProxy();
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Proxy function that calls {@link BluetoothAdapter#isOffloadedFilteringSupported()}.
+ *
+ * @return whether the offloaded scan filtering is supported
+ */
+ public boolean isOffloadedScanFilteringSupported() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return adapter.isOffloadedFilteringSupported();
+ }
+
+ /**
+ * Allow unit tests to substitute BluetoothAdapterProxy with a test instance
+ *
+ * @param proxy a test instance of the BluetoothAdapterProxy
+ */
+ @VisibleForTesting
+ public static void setInstanceForTesting(BluetoothAdapterProxy proxy) {
+ Utils.enforceInstrumentationTestMode();
+ synchronized (INSTANCE_LOCK) {
+ Log.d(TAG, "setInstanceForTesting(), set to " + proxy);
+ sInstance = proxy;
+ }
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
index d62d7ba06d434422979938a4da776eec3e3c037a..713545fbb21447c17999cbcff10bf50f265e7414 100644
--- a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -28,6 +28,7 @@ import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.OobData;
import android.content.Intent;
import android.os.Build;
+import android.os.Bundle;
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
@@ -48,6 +49,7 @@ import com.android.internal.util.StateMachine;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
/**
@@ -86,6 +88,7 @@ final class BondStateMachine extends StateMachine {
public static final String OOBDATAP192 = "oobdatap192";
public static final String OOBDATAP256 = "oobdatap256";
+ public static final String DISPLAY_PASSKEY = "display_passkey";
@VisibleForTesting Set mPendingBondedDevices = new HashSet<>();
@@ -249,7 +252,14 @@ final class BondStateMachine extends StateMachine {
case SSP_REQUEST:
int passkey = msg.arg1;
int variant = msg.arg2;
- sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
+ boolean displayPasskey =
+ (msg.getData() != null)
+ ? msg.getData().getByte(DISPLAY_PASSKEY) == 1 /* 1 == true */
+ : false;
+ sendDisplayPinIntent(
+ devProp.getAddress(),
+ displayPasskey ? Optional.of(passkey) : Optional.empty(),
+ variant);
break;
case PIN_REQUEST:
BluetoothClass btClass = dev.getBluetoothClass();
@@ -264,18 +274,24 @@ final class BondStateMachine extends StateMachine {
// Generate a variable 6-digit PIN in range of 100000-999999
// This is not truly random but good enough.
int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000)));
- sendDisplayPinIntent(devProp.getAddress(), pin,
+ sendDisplayPinIntent(
+ devProp.getAddress(),
+ Optional.of(pin),
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
break;
}
if (msg.arg2 == 1) { // Minimum 16 digit pin required here
- sendDisplayPinIntent(devProp.getAddress(), 0,
+ sendDisplayPinIntent(
+ devProp.getAddress(),
+ Optional.empty(),
BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS);
} else {
// In PIN_REQUEST, there is no passkey to display.So do not send the
- // EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
- sendDisplayPinIntent(devProp.getAddress(), 0,
+ // EXTRA_PAIRING_KEY type in the intent
+ sendDisplayPinIntent(
+ devProp.getAddress(),
+ Optional.empty(),
BluetoothDevice.PAIRING_VARIANT_PIN);
}
break;
@@ -326,9 +342,19 @@ final class BondStateMachine extends StateMachine {
boolean result;
// If we have some data
if (remoteP192Data != null || remoteP256Data != null) {
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
+ BluetoothDevice.BOND_BONDING,
+ BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_START_PAIRING_OOB,
+ BluetoothProtoEnums.UNBOND_REASON_UNKNOWN, mAdapterService.getMetricId(dev));
result = mAdapterService.createBondOutOfBandNative(addr, transport,
remoteP192Data, remoteP256Data);
} else {
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
+ BluetoothDevice.BOND_BONDING,
+ BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_START_PAIRING,
+ BluetoothProtoEnums.UNBOND_REASON_UNKNOWN, mAdapterService.getMetricId(dev));
result = mAdapterService.createBondNative(addr, transport);
}
BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED,
@@ -358,12 +384,10 @@ final class BondStateMachine extends StateMachine {
return false;
}
- private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
+ private void sendDisplayPinIntent(byte[] address, Optional maybePin, int variant) {
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
- if (pin != 0) {
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
- }
+ maybePin.ifPresent(pin -> intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// Workaround for Android Auto until pre-accepting pairing requests is added.
@@ -531,6 +555,9 @@ final class BondStateMachine extends StateMachine {
msg.obj = device;
if (displayPasskey) {
msg.arg1 = passkey;
+ Bundle bundle = new Bundle();
+ bundle.putByte(BondStateMachine.DISPLAY_PASSKEY, (byte) 1 /* true */);
+ msg.setData(bundle);
}
msg.arg2 = variant;
sendMessage(msg);
diff --git a/android/app/src/com/android/bluetooth/btservice/Config.java b/android/app/src/com/android/bluetooth/btservice/Config.java
index 1c021591bbb29501c65fb1f4c50fdf936c5e802d..c1493294a8bf92db7e1d82d0cd51eb9191eba4a5 100644
--- a/android/app/src/com/android/bluetooth/btservice/Config.java
+++ b/android/app/src/com/android/bluetooth/btservice/Config.java
@@ -60,10 +60,11 @@ public class Config {
private static final String FEATURE_HEARING_AID = "settings_bluetooth_hearing_aid";
private static final String FEATURE_BATTERY = "settings_bluetooth_battery";
- private static long sSupportedMask = 0;
private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY =
"ro.bluetooth.leaudio_switcher.supported";
+ private static final String LE_AUDIO_BROADCAST_DYNAMIC_SWITCH_PROPERTY =
+ "ro.bluetooth.leaudio_broadcast_switcher.supported";
private static final String LE_AUDIO_DYNAMIC_ENABLED_PROPERTY =
"persist.bluetooth.leaudio_switcher.enabled";
@@ -165,6 +166,11 @@ public class Config {
private static boolean sIsGdEnabledUptoScanningLayer = false;
static void init(Context ctx) {
+ if (LeAudioService.isBroadcastEnabled()) {
+ updateSupportedProfileMask(
+ true, LeAudioService.class, BluetoothProfile.LE_AUDIO_BROADCAST);
+ }
+
final boolean leAudioDynamicSwitchSupported =
SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false);
@@ -205,6 +211,15 @@ public class Config {
setProfileEnabled(TbsService.class, enable);
setProfileEnabled(McpService.class, enable);
setProfileEnabled(VolumeControlService.class, enable);
+
+ final boolean broadcastDynamicSwitchSupported =
+ SystemProperties.getBoolean(LE_AUDIO_BROADCAST_DYNAMIC_SWITCH_PROPERTY, false);
+
+ if (broadcastDynamicSwitchSupported) {
+ setProfileEnabled(BassClientService.class, enable);
+ updateSupportedProfileMask(
+ enable, LeAudioService.class, BluetoothProfile.LE_AUDIO_BROADCAST);
+ }
}
/**
@@ -226,8 +241,17 @@ public class Config {
sSupportedProfiles = profilesList.toArray(new Class[profilesList.size()]);
}
- static void addSupportedProfile(int supportedProfile) {
- sSupportedMask |= (1 << supportedProfile);
+ static void updateSupportedProfileMask(Boolean enable, Class profile, int supportedProfile) {
+ for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
+ if (config.mClass == profile) {
+ if (enable) {
+ config.mMask |= 1 << supportedProfile;
+ } else {
+ config.mMask &= ~(1 << supportedProfile);
+ }
+ return;
+ }
+ }
}
static HashSet geLeAudioUnicastProfiles() {
@@ -253,7 +277,7 @@ public class Config {
}
static long getSupportedProfilesBitMask() {
- long mask = sSupportedMask;
+ long mask = 0;
for (final Class profileClass : getSupportedProfiles()) {
mask |= getProfileMask(profileClass);
}
diff --git a/android/app/src/com/android/bluetooth/btservice/DataMigration.java b/android/app/src/com/android/bluetooth/btservice/DataMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..4846d606cfbb49db4beef0b939aff2eb5f3a69a8
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/DataMigration.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2022 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.btservice;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.bluetooth.btservice.storage.BluetoothDatabaseMigration;
+import com.android.bluetooth.opp.BluetoothOppProvider;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+final class DataMigration {
+ private DataMigration(){}
+ private static final String TAG = "DataMigration";
+
+ @VisibleForTesting
+ static final String AUTHORITY = "bluetooth_legacy.provider";
+
+ @VisibleForTesting
+ static final String START_MIGRATION_CALL = "start_legacy_migration";
+ @VisibleForTesting
+ static final String FINISH_MIGRATION_CALL = "finish_legacy_migration";
+
+ @VisibleForTesting
+ static final String BLUETOOTH_DATABASE = "bluetooth_db";
+ @VisibleForTesting
+ static final String OPP_DATABASE = "btopp.db";
+
+ // AvrcpVolumeManager.VOLUME_MAP
+ private static final String VOLUME_MAP_PREFERENCE_FILE = "bluetooth_volume_map";
+ // com.android.blueotooth.opp.Constants.BLUETOOTHOPP_CHANNEL_PREFERENCE
+ private static final String BLUETOOTHOPP_CHANNEL_PREFERENCE = "btopp_channels";
+
+ // com.android.blueotooth.opp.Constants.BLUETOOTHOPP_NAME_PREFERENCE
+ private static final String BLUETOOTHOPP_NAME_PREFERENCE = "btopp_names";
+
+ // com.android.blueotooth.opp.OPP_PREFERENCE_FILE
+ private static final String OPP_PREFERENCE_FILE = "OPPMGR";
+
+ @VisibleForTesting
+ static final String[] sharedPreferencesKeys = {
+ // Bundles of Boolean
+ AdapterService.PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
+ AdapterService.MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
+ AdapterService.SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
+
+ // Bundles of Integer
+ VOLUME_MAP_PREFERENCE_FILE,
+ BLUETOOTHOPP_CHANNEL_PREFERENCE,
+
+ // Bundles of String
+ BLUETOOTHOPP_NAME_PREFERENCE,
+
+ // Bundle of Boolean and String
+ OPP_PREFERENCE_FILE,
+ };
+
+ // Main key use for storing all the key in the associate bundle
+ @VisibleForTesting
+ static final String KEY_LIST = "key_list";
+
+ @VisibleForTesting
+ static final String BLUETOOTH_CONFIG = "bluetooth_config";
+ static final String MIGRATION_DONE_PROPERTY = "migration_done";
+ @VisibleForTesting
+ static final String MIGRATION_ATTEMPT_PROPERTY = "migration_attempt";
+
+ @VisibleForTesting
+ public static final int MIGRATION_STATUS_TO_BE_DONE = 0;
+ @VisibleForTesting
+ public static final int MIGRATION_STATUS_COMPLETED = 1;
+ @VisibleForTesting
+ public static final int MIGRATION_STATUS_MISSING_APK = 2;
+ @VisibleForTesting
+ public static final int MIGRATION_STATUS_MAX_ATTEMPT = 3;
+
+ @VisibleForTesting
+ static final int MAX_ATTEMPT = 3;
+
+ static int run(Context ctx) {
+ if (migrationStatus(ctx) == MIGRATION_STATUS_COMPLETED) {
+ Log.d(TAG, "Legacy migration skiped: already completed");
+ return MIGRATION_STATUS_COMPLETED;
+ }
+ if (!isMigrationApkInstalled(ctx)) {
+ Log.d(TAG, "Legacy migration skiped: no migration app installed");
+ markMigrationStatus(ctx, MIGRATION_STATUS_MISSING_APK);
+ return MIGRATION_STATUS_MISSING_APK;
+ }
+ if (!incrementeMigrationAttempt(ctx)) {
+ Log.d(TAG, "Legacy migration skiped: still failing after too many attempt");
+ markMigrationStatus(ctx, MIGRATION_STATUS_MAX_ATTEMPT);
+ return MIGRATION_STATUS_MAX_ATTEMPT;
+ }
+
+ for (String pref: sharedPreferencesKeys) {
+ sharedPreferencesMigration(pref, ctx);
+ }
+ // Migration for DefaultSharedPreferences used in PbapUtils. Contains Long
+ sharedPreferencesMigration(ctx.getPackageName() + "_preferences", ctx);
+
+ bluetoothDatabaseMigration(ctx);
+ oppDatabaseMigration(ctx);
+
+ markMigrationStatus(ctx, MIGRATION_STATUS_COMPLETED);
+ Log.d(TAG, "Legacy migration completed");
+ return MIGRATION_STATUS_COMPLETED;
+ }
+
+ @VisibleForTesting
+ static boolean bluetoothDatabaseMigration(Context ctx) {
+ final String logHeader = BLUETOOTH_DATABASE + ": ";
+ ContentResolver resolver = ctx.getContentResolver();
+ Cursor cursor = resolver.query(
+ Uri.parse("content://" + AUTHORITY + "/" + BLUETOOTH_DATABASE),
+ null, null, null, null);
+ if (cursor == null) {
+ Log.d(TAG, logHeader + "Nothing to migrate");
+ return true;
+ }
+ boolean status = BluetoothDatabaseMigration.run(ctx, cursor);
+ cursor.close();
+ if (status) {
+ resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, BLUETOOTH_DATABASE, null);
+ Log.d(TAG, logHeader + "Migration complete. File is deleted");
+ } else {
+ Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+ }
+ return status;
+ }
+
+ @VisibleForTesting
+ static boolean oppDatabaseMigration(Context ctx) {
+ final String logHeader = OPP_DATABASE + ": ";
+ ContentResolver resolver = ctx.getContentResolver();
+ Cursor cursor = resolver.query(
+ Uri.parse("content://" + AUTHORITY + "/" + OPP_DATABASE),
+ null, null, null, null);
+ if (cursor == null) {
+ Log.d(TAG, logHeader + "Nothing to migrate");
+ return true;
+ }
+ boolean status = BluetoothOppProvider.oppDatabaseMigration(ctx, cursor);
+ cursor.close();
+ if (status) {
+ resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, OPP_DATABASE, null);
+ Log.d(TAG, logHeader + "Migration complete. File is deleted");
+ } else {
+ Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+ }
+ return status;
+ }
+
+ private static boolean writeObjectToEditor(SharedPreferences.Editor editor, Bundle b,
+ String itemKey) {
+ Object value = b.get(itemKey);
+ if (value == null) {
+ Log.e(TAG, itemKey + ": No value associated with this itemKey");
+ return false;
+ }
+ if (value instanceof Boolean) {
+ editor.putBoolean(itemKey, (Boolean) value);
+ } else if (value instanceof Integer) {
+ editor.putInt(itemKey, (Integer) value);
+ } else if (value instanceof Long) {
+ editor.putLong(itemKey, (Long) value);
+ } else if (value instanceof String) {
+ editor.putString(itemKey, (String) value);
+ } else {
+ Log.e(TAG, itemKey + ": Failed to migrate: "
+ + value.getClass().getSimpleName() + ": Data type not handled");
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ static boolean sharedPreferencesMigration(String prefKey, Context ctx) {
+ final String logHeader = "SharedPreferencesMigration - " + prefKey + ": ";
+ ContentResolver resolver = ctx.getContentResolver();
+ Bundle b = resolver.call(AUTHORITY, START_MIGRATION_CALL, prefKey, null);
+ if (b == null) {
+ Log.d(TAG, logHeader + "Nothing to migrate");
+ return true;
+ }
+ List keys = b.getStringArrayList(KEY_LIST);
+ if (keys == null) {
+ Log.e(TAG, logHeader + "Wrong format of bundle: No keys to migrate");
+ return false;
+ }
+ SharedPreferences pref = ctx.getSharedPreferences(prefKey, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ boolean status = true;
+ for (String itemKey : keys) {
+ // prevent overriding any user settings if it's a new attempt
+ if (!pref.contains(itemKey)) {
+ status &= writeObjectToEditor(editor, b, itemKey);
+ } else {
+ Log.d(TAG, logHeader + itemKey + ": Already exists, not overriding data.");
+ }
+ }
+ editor.apply();
+ if (status) {
+ resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, prefKey, null);
+ Log.d(TAG, logHeader + "Migration complete. File is deleted");
+ } else {
+ Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+ }
+ return status;
+ }
+
+ @VisibleForTesting
+ static int migrationStatus(Context ctx) {
+ SharedPreferences pref = ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE);
+ return pref.getInt(MIGRATION_DONE_PROPERTY, MIGRATION_STATUS_TO_BE_DONE);
+ }
+
+ @VisibleForTesting
+ static boolean incrementeMigrationAttempt(Context ctx) {
+ SharedPreferences pref = ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE);
+ int currentAttempt = Math.min(pref.getInt(MIGRATION_ATTEMPT_PROPERTY, 0), MAX_ATTEMPT);
+ pref.edit()
+ .putInt(MIGRATION_ATTEMPT_PROPERTY, currentAttempt + 1)
+ .apply();
+ return currentAttempt < MAX_ATTEMPT;
+ }
+
+ @VisibleForTesting
+ static boolean isMigrationApkInstalled(Context ctx) {
+ ContentResolver resolver = ctx.getContentResolver();
+ ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY);
+ if (client != null) {
+ client.close();
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ static void markMigrationStatus(Context ctx, int status) {
+ ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE)
+ .edit()
+ .putInt(MIGRATION_DONE_PROPERTY, status)
+ .apply();
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
index 12a4f8107c02124c4902aae6e13d0e49f2eea3a9..ecbac6062a117a85b7b70d70def058f958c3dff2 100644
--- a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
+++ b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
@@ -16,11 +16,7 @@
package com.android.bluetooth.btservice;
import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Log;
@@ -39,11 +35,6 @@ public class MetricsLogger {
public static final boolean DEBUG = false;
- /**
- * Intent indicating Bluetooth counter metrics should send logs to BluetoothStatsLog
- */
- public static final String BLUETOOTH_COUNTER_METRICS_ACTION =
- "com.android.bluetooth.btservice.BLUETOOTH_COUNTER_METRICS_ACTION";
// 6 hours timeout for counter metrics
private static final long BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS = 6L * 3600L * 1000L;
@@ -56,16 +47,11 @@ public class MetricsLogger {
private boolean mInitialized = false;
static final private Object mLock = new Object();
- private BroadcastReceiver mDrainReceiver = new BroadcastReceiver() {
+ private AlarmManager.OnAlarmListener mOnAlarmListener = new AlarmManager.OnAlarmListener () {
@Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (DEBUG) {
- Log.d(TAG, "onReceive: " + action);
- }
- if (action.equals(BLUETOOTH_COUNTER_METRICS_ACTION)) {
- drainBufferedCounters();
- }
+ public void onAlarm() {
+ drainBufferedCounters();
+ scheduleDrains();
}
};
@@ -90,14 +76,11 @@ public class MetricsLogger {
}
mInitialized = true;
mContext = context;
- IntentFilter filter = new IntentFilter();
- filter.addAction(BLUETOOTH_COUNTER_METRICS_ACTION);
- mContext.registerReceiver(mDrainReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
scheduleDrains();
return true;
}
- public boolean count(int key, long count) {
+ public boolean cacheCount(int key, long count) {
if (!mInitialized) {
Log.w(TAG, "MetricsLogger isn't initialized");
return false;
@@ -154,22 +137,30 @@ public class MetricsLogger {
}
protected void scheduleDrains() {
- if (DEBUG) {
- Log.d(TAG, "setCounterMetricsAlarm()");
- }
+ Log.i(TAG, "setCounterMetricsAlarm()");
if (mAlarmManager == null) {
mAlarmManager = mContext.getSystemService(AlarmManager.class);
}
- mAlarmManager.setRepeating(
+ mAlarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime(),
- BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS,
- getDrainIntent());
+ SystemClock.elapsedRealtime() + BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS,
+ TAG,
+ mOnAlarmListener,
+ null);
}
- protected void writeCounter(int key, long count) {
+ public boolean count(int key, long count) {
+ if (!mInitialized) {
+ Log.w(TAG, "MetricsLogger isn't initialized");
+ return false;
+ }
+ if (count <= 0) {
+ Log.w(TAG, "count is not larger than 0. count: " + count + " key: " + key);
+ return false;
+ }
BluetoothStatsLog.write(
BluetoothStatsLog.BLUETOOTH_CODE_PATH_COUNTER, key, count);
+ return true;
}
protected void drainBufferedCounters() {
@@ -177,7 +168,7 @@ public class MetricsLogger {
synchronized (mLock) {
// send mCounters to statsd
for (int key : mCounters.keySet()) {
- writeCounter(key, mCounters.get(key));
+ count(key, mCounters.get(key));
}
mCounters.clear();
}
@@ -198,15 +189,6 @@ public class MetricsLogger {
return true;
}
protected void cancelPendingDrain() {
- PendingIntent pIntent = getDrainIntent();
- pIntent.cancel();
- mAlarmManager.cancel(pIntent);
- }
-
- private PendingIntent getDrainIntent() {
- Intent counterMetricsIntent = new Intent(BLUETOOTH_COUNTER_METRICS_ACTION);
- counterMetricsIntent.setPackage(mContext.getPackageName());
- return PendingIntent.getBroadcast(
- mContext, 0, counterMetricsIntent, PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.cancel(mOnAlarmListener);
}
}
diff --git a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
index c81a5fbd69c2dc339d7c8728181bbd40fdab144e..163a3cdf7d716bb0d2df39dcc1520a37628588a8 100644
--- a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -576,6 +576,7 @@ class PhonePolicy {
+ " attempting auto connection");
autoConnectHeadset(mostRecentlyActiveA2dpDevice);
autoConnectA2dp(mostRecentlyActiveA2dpDevice);
+ autoConnectHidHost(mostRecentlyActiveA2dpDevice);
} else {
debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
}
@@ -614,6 +615,23 @@ class PhonePolicy {
}
}
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ private void autoConnectHidHost(BluetoothDevice device) {
+ final HidHostService hidHostService = mFactory.getHidHostService();
+ if (hidHostService == null) {
+ warnLog("autoConnectHidHost: service is null, failed to connect to " + device);
+ return;
+ }
+ int hidHostConnectionPolicy = hidHostService.getConnectionPolicy(device);
+ if (hidHostConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ debugLog("autoConnectHidHost: Connecting HID with " + device);
+ hidHostService.connect(device);
+ } else {
+ debugLog("autoConnectHidHost: skipped auto-connect HID with device " + device
+ + " connectionPolicy " + hidHostConnectionPolicy);
+ }
+ }
+
private void connectOtherProfile(BluetoothDevice device) {
if (mAdapterService.isQuietModeEnabled()) {
debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
@@ -657,6 +675,7 @@ class PhonePolicy {
VolumeControlService volumeControlService =
mFactory.getVolumeControlService();
BatteryService batteryService = mFactory.getBatteryService();
+ HidHostService hidHostService = mFactory.getHidHostService();
if (hsService != null) {
if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device)
@@ -730,6 +749,15 @@ class PhonePolicy {
batteryService.connect(device);
}
}
+ if (hidHostService != null) {
+ if ((hidHostService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+ && (hidHostService.getConnectionState(device)
+ == BluetoothProfile.STATE_DISCONNECTED)) {
+ debugLog("Retrying connection to HID with device " + device);
+ hidHostService.connect(device);
+ }
+ }
}
private static void debugLog(String msg) {
diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
index 9c8c88be4bbc7cca0ab173b98630515791fe59ef..90d10ee03d8b60cd69658bd0c9df79fe6b493610 100644
--- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -19,6 +19,7 @@ package com.android.bluetooth.btservice;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_SCAN;
+import android.app.admin.SecurityLog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothClass;
@@ -850,6 +851,8 @@ final class RemoteDevices {
if (batteryService != null) {
batteryService.connect(device);
}
+ SecurityLog.writeEvent(SecurityLog.TAG_BLUETOOTH_CONNECTION,
+ Utils.getLoggableAddress(device), /* success */ 1, /* reason */ "");
debugLog(
"aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
+ " Connected: " + device);
@@ -883,6 +886,10 @@ final class RemoteDevices {
deviceProp.setBondingInitiatedLocally(false);
}
}
+ SecurityLog.writeEvent(SecurityLog.TAG_BLUETOOTH_DISCONNECTION,
+ Utils.getLoggableAddress(device),
+ BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonToString(
+ AdapterService.hciToAndroidDisconnectReason(hciReason)));
debugLog(
"aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
+ " Disconnected: " + device
diff --git a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java
index 3d924c3e9441f0ffb048d84cfbf756ec1c643b56..b9cb9d778d9e9a25ebababd65026caaaefe09f28 100644
--- a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java
@@ -25,6 +25,8 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import java.util.Arrays;
+
/** ActivityAttribution Native Interface to/from JNI. */
public class ActivityAttributionNativeInterface {
private static final boolean DBG = false;
@@ -73,7 +75,7 @@ public class ActivityAttributionNativeInterface {
}
private void onActivityLogsReady(byte[] logs) {
- Log.i(TAG, "onActivityLogsReady() BTAA: " + logs);
+ Log.i(TAG, "onActivityLogsReady() BTAA: " + Arrays.toString(logs));
}
// Native methods that call into the JNI interface
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java b/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..a56c6a4322e1ce92046597982bf17cf65494f715
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2022 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.btservice.storage;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUtils;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class for regrouping the migration that occur when going mainline
+ * @hide
+ */
+public final class BluetoothDatabaseMigration {
+ private static final String TAG = "BluetoothDatabaseMigration";
+ /**
+ * @hide
+ */
+ public static boolean run(Context ctx, Cursor cursor) {
+ boolean result = true;
+ MetadataDatabase database = MetadataDatabase.createDatabaseWithoutMigration(ctx);
+ while (cursor.moveToNext()) {
+ try {
+ final String primaryKey = cursor.getString(cursor.getColumnIndexOrThrow("address"));
+ String logKey = BluetoothUtils.toAnonymizedAddress(primaryKey);
+ if (logKey == null) { // handle non device address
+ logKey = primaryKey;
+ }
+
+ Metadata metadata = new Metadata(primaryKey);
+
+ metadata.migrated = fetchInt(cursor, "migrated") > 0;
+ migrate_a2dpSupportsOptionalCodecs(cursor, logKey, metadata);
+ migrate_a2dpOptionalCodecsEnabled(cursor, logKey, metadata);
+ metadata.last_active_time = fetchInt(cursor, "last_active_time");
+ metadata.is_active_a2dp_device = fetchInt(cursor, "is_active_a2dp_device") > 0;
+ migrate_connectionPolicy(cursor, logKey, metadata);
+ migrate_customizedMeta(cursor, metadata);
+
+ database.insert(metadata);
+ Log.d(TAG, "One item migrated: " + metadata);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to migrate one item: " + e);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ private static final List> CONNECTION_POLICIES =
+ Arrays.asList(
+ new Pair(BluetoothProfile.A2DP, "a2dp_connection_policy"),
+ new Pair(BluetoothProfile.A2DP_SINK, "a2dp_sink_connection_policy"),
+ new Pair(BluetoothProfile.HEADSET, "hfp_connection_policy"),
+ new Pair(BluetoothProfile.HEADSET_CLIENT, "hfp_client_connection_policy"),
+ new Pair(BluetoothProfile.HID_HOST, "hid_host_connection_policy"),
+ new Pair(BluetoothProfile.PAN, "pan_connection_policy"),
+ new Pair(BluetoothProfile.PBAP, "pbap_connection_policy"),
+ new Pair(BluetoothProfile.PBAP_CLIENT, "pbap_client_connection_policy"),
+ new Pair(BluetoothProfile.MAP, "map_connection_policy"),
+ new Pair(BluetoothProfile.SAP, "sap_connection_policy"),
+ new Pair(BluetoothProfile.HEARING_AID, "hearing_aid_connection_policy"),
+ new Pair(BluetoothProfile.HAP_CLIENT, "hap_client_connection_policy"),
+ new Pair(BluetoothProfile.MAP_CLIENT, "map_client_connection_policy"),
+ new Pair(BluetoothProfile.LE_AUDIO, "le_audio_connection_policy"),
+ new Pair(BluetoothProfile.VOLUME_CONTROL, "volume_control_connection_policy"),
+ new Pair(BluetoothProfile.CSIP_SET_COORDINATOR,
+ "csip_set_coordinator_connection_policy"),
+ new Pair(BluetoothProfile.LE_CALL_CONTROL, "le_call_control_connection_policy"),
+ new Pair(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+ "bass_client_connection_policy"),
+ new Pair(BluetoothProfile.BATTERY, "battery_connection_policy")
+ );
+
+ private static final List> CUSTOMIZED_META_KEYS =
+ Arrays.asList(
+ new Pair(BluetoothDevice.METADATA_MANUFACTURER_NAME, "manufacturer_name"),
+ new Pair(BluetoothDevice.METADATA_MODEL_NAME, "model_name"),
+ new Pair(BluetoothDevice.METADATA_SOFTWARE_VERSION, "software_version"),
+ new Pair(BluetoothDevice.METADATA_HARDWARE_VERSION, "hardware_version"),
+ new Pair(BluetoothDevice.METADATA_COMPANION_APP, "companion_app"),
+ new Pair(BluetoothDevice.METADATA_MAIN_ICON, "main_icon"),
+ new Pair(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET, "is_untethered_headset"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON, "untethered_left_icon"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON, "untethered_right_icon"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_ICON, "untethered_case_icon"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+ "untethered_left_battery"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+ "untethered_right_battery"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+ "untethered_case_battery"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+ "untethered_left_charging"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+ "untethered_right_charging"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+ "untethered_case_charging"),
+ new Pair(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+ "enhanced_settings_ui_uri"),
+ new Pair(BluetoothDevice.METADATA_DEVICE_TYPE, "device_type"),
+ new Pair(BluetoothDevice.METADATA_MAIN_BATTERY, "main_battery"),
+ new Pair(BluetoothDevice.METADATA_MAIN_CHARGING, "main_charging"),
+ new Pair(BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+ "main_low_battery_threshold"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+ "untethered_left_low_battery_threshold"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+ "untethered_right_low_battery_threshold"),
+ new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
+ "untethered_case_low_battery_threshold"),
+ new Pair(BluetoothDevice.METADATA_SPATIAL_AUDIO, "spatial_audio"),
+ new Pair(BluetoothDevice.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ "fastpair_customized")
+ );
+
+ private static int fetchInt(Cursor cursor, String key) {
+ return cursor.getInt(cursor.getColumnIndexOrThrow(key));
+ }
+
+ private static void migrate_a2dpSupportsOptionalCodecs(Cursor cursor, String logKey,
+ Metadata metadata) {
+ final String key = "a2dpSupportsOptionalCodecs";
+ final List allowedValue = new ArrayList<>(Arrays.asList(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED));
+ final int value = fetchInt(cursor, key);
+ if (!allowedValue.contains(value)) {
+ throw new IllegalArgumentException(logKey + ": Bad value for [" + key + "]: " + value);
+ }
+ metadata.a2dpSupportsOptionalCodecs = value;
+ }
+
+ private static void migrate_a2dpOptionalCodecsEnabled(Cursor cursor, String logKey,
+ Metadata metadata) {
+ final String key = "a2dpOptionalCodecsEnabled";
+ final List allowedValue = new ArrayList<>(Arrays.asList(
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED));
+ final int value = fetchInt(cursor, key);
+ if (!allowedValue.contains(value)) {
+ throw new IllegalArgumentException(logKey + ": Bad value for [" + key + "]: " + value);
+ }
+ metadata.a2dpOptionalCodecsEnabled = value;
+ }
+
+ private static void migrate_connectionPolicy(Cursor cursor, String logKey,
+ Metadata metadata) {
+ final List allowedValue = new ArrayList<>(Arrays.asList(
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+ for (Pair p : CONNECTION_POLICIES) {
+ final int policy = cursor.getInt(cursor.getColumnIndexOrThrow(p.second));
+ if (allowedValue.contains(policy)) {
+ metadata.setProfileConnectionPolicy(p.first, policy);
+ } else {
+ throw new IllegalArgumentException(logKey + ": Bad value for ["
+ + BluetoothProfile.getProfileName(p.first)
+ + "]: " + policy);
+ }
+ }
+ }
+
+ private static void migrate_customizedMeta(Cursor cursor, Metadata metadata) {
+ for (Pair p : CUSTOMIZED_META_KEYS) {
+ final byte[] blob = cursor.getBlob(cursor.getColumnIndexOrThrow(p.second));
+ // There is no specific pattern to check the custom meta data
+ metadata.setCustomizedMeta(p.first, blob);
+ }
+ }
+
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
index 554e0753fc3b923ddf8a646100d7452321e04fc4..b3746a2fb58c680691ec361cd486c0466319f5c7 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -46,6 +46,7 @@ class CustomizedMetadataEntity {
public byte[] untethered_case_low_battery_threshold;
public byte[] spatial_audio;
public byte[] fastpair_customized;
+ public byte[] le_audio;
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -100,7 +101,10 @@ class CustomizedMetadataEntity {
.append("|spatial_audio=")
.append(metadataToString(spatial_audio))
.append("|fastpair_customized=")
- .append(metadataToString(fastpair_customized));
+ .append(metadataToString(fastpair_customized))
+ .append("|le_audio=")
+ .append(metadataToString(le_audio));
+
return builder.toString();
}
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index 4bc32b414d4f7d6ac70fa800db6477318432108e..fa850499d0e171ea8fee70be04058a70dedb13c1 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -637,6 +637,38 @@ public class DatabaseManager {
return mostRecentlyConnectedDevices;
}
+ /**
+ * Gets the most recently connected bluetooth device in a given list.
+ *
+ * @param devicesList the list of {@link BluetoothDevice} to search in
+ * @return the most recently connected {@link BluetoothDevice} in the given
+ * {@code devicesList}, or null if an error occurred
+ *
+ * @hide
+ */
+ public BluetoothDevice getMostRecentlyConnectedDevicesInList(
+ List devicesList) {
+ if (devicesList == null) {
+ return null;
+ }
+
+ BluetoothDevice mostRecentDevice = null;
+ long mostRecentLastActiveTime = -1;
+ synchronized (mMetadataCache) {
+ for (BluetoothDevice device : devicesList) {
+ String address = device.getAddress();
+ Metadata metadata = mMetadataCache.get(address);
+ if (metadata != null && (mostRecentLastActiveTime == -1
+ || mostRecentLastActiveTime < metadata.last_active_time)) {
+ mostRecentLastActiveTime = metadata.last_active_time;
+ mostRecentDevice = device;
+ }
+
+ }
+ }
+ return mostRecentDevice;
+ }
+
/**
* Gets the last active a2dp device
*
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
index 91e33e044a0acdbf7fd259937ee109a73c595617..f64f35e6861df9fd75d346bdec9f2436e475b93a 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -21,17 +21,24 @@ import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUtils;
import androidx.annotation.NonNull;
import androidx.room.Embedded;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
+/**
+ * @hide
+ */
@Entity(tableName = "metadata")
-class Metadata {
+@VisibleForTesting
+public class Metadata {
@PrimaryKey
@NonNull
private String address;
@@ -62,7 +69,11 @@ class Metadata {
is_active_a2dp_device = true;
}
- String getAddress() {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public String getAddress() {
return address;
}
@@ -75,7 +86,7 @@ class Metadata {
*/
@NonNull
public String getAnonymizedAddress() {
- return "XX:XX:XX" + getAddress().substring(8);
+ return BluetoothUtils.toAnonymizedAddress(address);
}
void setProfileConnectionPolicy(int profile, int connectionPolicy) {
@@ -148,7 +159,11 @@ class Metadata {
}
}
- int getProfileConnectionPolicy(int profile) {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public int getProfileConnectionPolicy(int profile) {
switch (profile) {
case BluetoothProfile.A2DP:
return profileConnectionPolicies.a2dp_connection_policy;
@@ -272,10 +287,17 @@ class Metadata {
case BluetoothDevice.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS:
publicMetadata.fastpair_customized = value;
break;
+ case BluetoothDevice.METADATA_LE_AUDIO:
+ publicMetadata.le_audio = value;
+ break;
}
}
- byte[] getCustomizedMeta(int key) {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public byte[] getCustomizedMeta(int key) {
byte[] value = null;
switch (key) {
case BluetoothDevice.METADATA_MANUFACTURER_NAME:
@@ -356,6 +378,9 @@ class Metadata {
case BluetoothDevice.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS:
value = publicMetadata.fastpair_customized;
break;
+ case BluetoothDevice.METADATA_LE_AUDIO:
+ value = publicMetadata.le_audio;
+ break;
}
return value;
}
@@ -372,7 +397,8 @@ class Metadata {
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(address)
+ builder.append(getAnonymizedAddress())
+ .append(" last_active_time=" + last_active_time)
.append(" {profile connection policy(")
.append(profileConnectionPolicies)
.append("), optional codec(support=")
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index a2db277c0d996c5719d58210328113ce89571b1a..a71a548e8eb6d5111da245652a41f2afc388d849 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -33,7 +33,7 @@ import java.util.List;
/**
* MetadataDatabase is a Room database stores Bluetooth persistence data
*/
-@Database(entities = {Metadata.class}, version = 113)
+@Database(entities = {Metadata.class}, version = 114)
public abstract class MetadataDatabase extends RoomDatabase {
/**
* The metadata database file name
@@ -66,6 +66,7 @@ public abstract class MetadataDatabase extends RoomDatabase {
.addMigrations(MIGRATION_110_111)
.addMigrations(MIGRATION_111_112)
.addMigrations(MIGRATION_112_113)
+ .addMigrations(MIGRATION_113_114)
.allowMainThreadQueries()
.build();
}
@@ -483,4 +484,20 @@ public abstract class MetadataDatabase extends RoomDatabase {
}
}
};
+
+ @VisibleForTesting
+ static final Migration MIGRATION_113_114 = new Migration(113, 114) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `le_audio` BLOB");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null || cursor.getColumnIndex("le_audio") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
}
diff --git a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
index f85dbbc4bff64bd1a86e0022030792b1365ee2bb..6c87ed0a32cbcbe72c1164fb2ffccd8b3f0edf34 100644
--- a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
+++ b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
@@ -278,6 +278,7 @@ public class CsipSetCoordinatorService extends ProfileService {
CsipSetCoordinatorStateMachine smConnect = getOrCreateStateMachine(device);
if (smConnect == null) {
Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+ return false;
}
smConnect.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
}
@@ -597,6 +598,24 @@ public class CsipSetCoordinatorService extends ProfileService {
.collect(Collectors.toList());
}
+ /**
+ * Get group ID for a given device and UUID
+ * @param device potential group member
+ * @param uuid profile context UUID
+ * @return group ID
+ */
+ public Integer getGroupId(BluetoothDevice device, ParcelUuid uuid) {
+ Map device_groups =
+ mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>());
+ return mGroupIdToUuidMap.entrySet()
+ .stream()
+ .filter(e -> (device_groups.containsKey(e.getKey())
+ && e.getValue().equals(uuid)))
+ .map(Map.Entry::getKey)
+ .findFirst()
+ .orElse(IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID);
+ }
+
/**
* Get device's groups/
* @param device group member device
@@ -888,6 +907,8 @@ public class CsipSetCoordinatorService extends ProfileService {
return;
}
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnecting device because it was unbonded.");
+ disconnect(device);
return;
}
removeStateMachine(device);
@@ -953,8 +974,11 @@ public class CsipSetCoordinatorService extends ProfileService {
private CsipSetCoordinatorService mService;
private CsipSetCoordinatorService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)) {
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
return null;
}
diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
index e02fd6f73f2849beb42c025ce29cb6d8d62d03ed..4ba889dbbcb9c6a083f2d24632643ab8372269c6 100644
--- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -30,6 +30,7 @@ import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.gatt.GattService.AdvertiserMap;
import java.util.Collections;
import java.util.HashMap;
@@ -46,6 +47,7 @@ class AdvertiseManager {
private final GattService mService;
private final AdapterService mAdapterService;
+ private final AdvertiserMap mAdvertiserMap;
private Handler mHandler;
Map mAdvertisers = Collections.synchronizedMap(new HashMap<>());
static int sTempRegistrationId = -1;
@@ -53,12 +55,14 @@ class AdvertiseManager {
/**
* Constructor of {@link AdvertiseManager}.
*/
- AdvertiseManager(GattService service, AdapterService adapterService) {
+ AdvertiseManager(GattService service, AdapterService adapterService,
+ AdvertiserMap advertiserMap) {
if (DBG) {
Log.d(TAG, "advertise manager created");
}
mService = service;
mAdapterService = adapterService;
+ mAdvertiserMap = advertiserMap;
}
/**
@@ -157,10 +161,18 @@ class AdvertiseManager {
if (status == 0) {
entry.setValue(
new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback));
+
+ mAdvertiserMap.setAdvertiserIdByRegId(regId, advertiserId);
} else {
IBinder binder = entry.getKey();
binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
mAdvertisers.remove(binder);
+
+ AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId);
+ if (stats != null) {
+ stats.recordAdvertiseStop();
+ }
+ mAdvertiserMap.removeAppAdvertiseStats(regId);
}
callback.onAdvertisingSetStarted(advertiserId, txPower, status);
@@ -181,6 +193,13 @@ class AdvertiseManager {
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onAdvertisingEnabled(advertiserId, enable, status);
+
+ if (!enable && status != 0) {
+ AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId);
+ if (stats != null) {
+ stats.recordAdvertiseStop();
+ }
+ }
}
void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
@@ -203,14 +222,19 @@ class AdvertiseManager {
byte[] periodicDataBytes =
AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
- int cbId = --sTempRegistrationId;
- mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback));
+ int cbId = --sTempRegistrationId;
+ mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback));
- if (DBG) {
- Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
- }
- startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, periodicParameters,
- periodicDataBytes, duration, maxExtAdvEvents, cbId);
+ if (DBG) {
+ Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
+ }
+
+ mAdvertiserMap.add(cbId, callback, mService);
+ mAdvertiserMap.recordAdvertiseStart(cbId, parameters, advertiseData,
+ scanResponse, periodicParameters, periodicData, duration, maxExtAdvEvents);
+
+ startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes,
+ periodicParameters, periodicDataBytes, duration, maxExtAdvEvents, cbId);
} catch (IllegalArgumentException e) {
try {
@@ -276,6 +300,8 @@ class AdvertiseManager {
} catch (RemoteException e) {
Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
}
+
+ mAdvertiserMap.recordAdvertiseStop(advertiserId);
}
void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
@@ -285,6 +311,9 @@ class AdvertiseManager {
return;
}
enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);
+
+ mAdvertiserMap.enableAdvertisingSet(advertiserId,
+ enable, duration, maxExtAdvEvents);
}
void setAdvertisingData(int advertiserId, AdvertiseData data) {
@@ -297,6 +326,8 @@ class AdvertiseManager {
try {
setAdvertisingDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+
+ mAdvertiserMap.setAdvertisingData(advertiserId, data);
} catch (IllegalArgumentException e) {
try {
onAdvertisingDataSet(advertiserId,
@@ -317,6 +348,8 @@ class AdvertiseManager {
try {
setScanResponseDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+
+ mAdvertiserMap.setScanResponseData(advertiserId, data);
} catch (IllegalArgumentException e) {
try {
onScanResponseDataSet(advertiserId,
@@ -334,6 +367,8 @@ class AdvertiseManager {
return;
}
setAdvertisingParametersNative(advertiserId, parameters);
+
+ mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters);
}
void setPeriodicAdvertisingParameters(int advertiserId,
@@ -344,6 +379,8 @@ class AdvertiseManager {
return;
}
setPeriodicAdvertisingParametersNative(advertiserId, parameters);
+
+ mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters);
}
void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
@@ -356,6 +393,8 @@ class AdvertiseManager {
try {
setPeriodicAdvertisingDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+
+ mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data);
} catch (IllegalArgumentException e) {
try {
onPeriodicAdvertisingDataSet(advertiserId,
@@ -473,6 +512,11 @@ class AdvertiseManager {
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
+
+ AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId);
+ if (stats != null) {
+ stats.onPeriodicAdvertiseEnabled(enable);
+ }
}
static {
diff --git a/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java b/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e0724623c1b2064819cc830dcfa3c1f3a5c9798
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2021 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.BluetoothDevice;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ScanStats class helps keep track of information about scans
+ * on a per application basis.
+ * @hide
+ */
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+public class AppAdvertiseStats {
+ private static final String TAG = AppAdvertiseStats.class.getSimpleName();
+
+ private static DateTimeFormatter sDateFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss")
+ .withZone(ZoneId.systemDefault());
+
+ static final String[] PHY_LE_STRINGS = {"LE_1M", "LE_2M", "LE_CODED"};
+ static final int UUID_STRING_FILTER_LEN = 8;
+
+ // ContextMap here is needed to grab Apps and Connections
+ ContextMap mContextMap;
+
+ // GattService is needed to add scan event protos to be dumped later
+ GattService mGattService;
+
+ static class AppAdvertiserData {
+ public boolean includeDeviceName = false;
+ public boolean includeTxPowerLevel = false;
+ public SparseArray manufacturerData;
+ public Map serviceData;
+ public List serviceUuids;
+ AppAdvertiserData(boolean includeDeviceName, boolean includeTxPowerLevel,
+ SparseArray manufacturerData, Map serviceData,
+ List serviceUuids) {
+ this.includeDeviceName = includeDeviceName;
+ this.includeTxPowerLevel = includeTxPowerLevel;
+ this.manufacturerData = manufacturerData;
+ this.serviceData = serviceData;
+ this.serviceUuids = serviceUuids;
+ }
+ }
+
+ static class AppAdvertiserRecord {
+ public Instant startTime = null;
+ public Instant stopTime = null;
+ public int duration = 0;
+ public int maxExtendedAdvertisingEvents = 0;
+ AppAdvertiserRecord(Instant startTime) {
+ this.startTime = startTime;
+ }
+ }
+
+ private int mAppUid;
+ private String mAppName;
+ private int mId;
+ private boolean mAdvertisingEnabled = false;
+ private boolean mPeriodicAdvertisingEnabled = false;
+ private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
+ private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
+ private int mInterval = 0;
+ private int mTxPowerLevel = 0;
+ private boolean mLegacy = false;
+ private boolean mAnonymous = false;
+ private boolean mConnectable = false;
+ private boolean mScannable = false;
+ private AppAdvertiserData mAdvertisingData = null;
+ private AppAdvertiserData mScanResponseData = null;
+ private AppAdvertiserData mPeriodicAdvertisingData = null;
+ private boolean mPeriodicIncludeTxPower = false;
+ private int mPeriodicInterval = 0;
+ public ArrayList mAdvertiserRecords =
+ new ArrayList();
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public AppAdvertiseStats(int appUid, int id, String name, ContextMap map, GattService service) {
+ this.mAppUid = appUid;
+ this.mId = id;
+ this.mAppName = name;
+ this.mContextMap = map;
+ this.mGattService = service;
+ }
+
+ void recordAdvertiseStart(AdvertisingSetParameters parameters,
+ AdvertiseData advertiseData, AdvertiseData scanResponse,
+ PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
+ int duration, int maxExtAdvEvents) {
+ mAdvertisingEnabled = true;
+ AppAdvertiserRecord record = new AppAdvertiserRecord(Instant.now());
+ record.duration = duration;
+ record.maxExtendedAdvertisingEvents = maxExtAdvEvents;
+ mAdvertiserRecords.add(record);
+ if (mAdvertiserRecords.size() > 5) {
+ mAdvertiserRecords.remove(0);
+ }
+
+ if (parameters != null) {
+ mPrimaryPhy = parameters.getPrimaryPhy();
+ mSecondaryPhy = parameters.getSecondaryPhy();
+ mInterval = parameters.getInterval();
+ mTxPowerLevel = parameters.getTxPowerLevel();
+ mLegacy = parameters.isLegacy();
+ mAnonymous = parameters.isAnonymous();
+ mConnectable = parameters.isConnectable();
+ mScannable = parameters.isScannable();
+ }
+
+ if (advertiseData != null) {
+ mAdvertisingData = new AppAdvertiserData(advertiseData.getIncludeDeviceName(),
+ advertiseData.getIncludeTxPowerLevel(),
+ advertiseData.getManufacturerSpecificData(),
+ advertiseData.getServiceData(),
+ advertiseData.getServiceUuids());
+ }
+
+ if (scanResponse != null) {
+ mScanResponseData = new AppAdvertiserData(scanResponse.getIncludeDeviceName(),
+ scanResponse.getIncludeTxPowerLevel(),
+ scanResponse.getManufacturerSpecificData(),
+ scanResponse.getServiceData(),
+ scanResponse.getServiceUuids());
+ }
+
+ if (periodicData != null) {
+ mPeriodicAdvertisingData = new AppAdvertiserData(
+ periodicData.getIncludeDeviceName(),
+ periodicData.getIncludeTxPowerLevel(),
+ periodicData.getManufacturerSpecificData(),
+ periodicData.getServiceData(),
+ periodicData.getServiceUuids());
+ }
+
+ if (periodicParameters != null) {
+ mPeriodicAdvertisingEnabled = true;
+ mPeriodicIncludeTxPower = periodicParameters.getIncludeTxPower();
+ mPeriodicInterval = periodicParameters.getInterval();
+ }
+ }
+
+ void recordAdvertiseStart(int duration, int maxExtAdvEvents) {
+ recordAdvertiseStart(null, null, null, null, null, duration, maxExtAdvEvents);
+ }
+
+ void recordAdvertiseStop() {
+ mAdvertisingEnabled = false;
+ mPeriodicAdvertisingEnabled = false;
+ if (!mAdvertiserRecords.isEmpty()) {
+ AppAdvertiserRecord record = mAdvertiserRecords.get(mAdvertiserRecords.size() - 1);
+ record.stopTime = Instant.now();
+ }
+ }
+
+ void enableAdvertisingSet(boolean enable, int duration, int maxExtAdvEvents) {
+ if (enable) {
+ //if the advertisingSet have not been disabled, skip enabling.
+ if (!mAdvertisingEnabled) {
+ recordAdvertiseStart(duration, maxExtAdvEvents);
+ }
+ } else {
+ //if the advertisingSet have not been enabled, skip disabling.
+ if (mAdvertisingEnabled) {
+ recordAdvertiseStop();
+ }
+ }
+ }
+
+ void setAdvertisingData(AdvertiseData data) {
+ if (mAdvertisingData == null) {
+ mAdvertisingData = new AppAdvertiserData(data.getIncludeDeviceName(),
+ data.getIncludeTxPowerLevel(),
+ data.getManufacturerSpecificData(),
+ data.getServiceData(),
+ data.getServiceUuids());
+ } else if (data != null) {
+ mAdvertisingData.includeDeviceName = data.getIncludeDeviceName();
+ mAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
+ mAdvertisingData.manufacturerData = data.getManufacturerSpecificData();
+ mAdvertisingData.serviceData = data.getServiceData();
+ mAdvertisingData.serviceUuids = data.getServiceUuids();
+ }
+ }
+
+ void setScanResponseData(AdvertiseData data) {
+ if (mScanResponseData == null) {
+ mScanResponseData = new AppAdvertiserData(data.getIncludeDeviceName(),
+ data.getIncludeTxPowerLevel(),
+ data.getManufacturerSpecificData(),
+ data.getServiceData(),
+ data.getServiceUuids());
+ } else if (data != null) {
+ mScanResponseData.includeDeviceName = data.getIncludeDeviceName();
+ mScanResponseData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
+ mScanResponseData.manufacturerData = data.getManufacturerSpecificData();
+ mScanResponseData.serviceData = data.getServiceData();
+ mScanResponseData.serviceUuids = data.getServiceUuids();
+ }
+ }
+
+ void setAdvertisingParameters(AdvertisingSetParameters parameters) {
+ if (parameters != null) {
+ mPrimaryPhy = parameters.getPrimaryPhy();
+ mSecondaryPhy = parameters.getSecondaryPhy();
+ mInterval = parameters.getInterval();
+ mTxPowerLevel = parameters.getTxPowerLevel();
+ mLegacy = parameters.isLegacy();
+ mAnonymous = parameters.isAnonymous();
+ mConnectable = parameters.isConnectable();
+ mScannable = parameters.isScannable();
+ }
+ }
+
+ void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
+ if (parameters != null) {
+ mPeriodicIncludeTxPower = parameters.getIncludeTxPower();
+ mPeriodicInterval = parameters.getInterval();
+ }
+ }
+
+ void setPeriodicAdvertisingData(AdvertiseData data) {
+ if (mPeriodicAdvertisingData == null) {
+ mPeriodicAdvertisingData = new AppAdvertiserData(data.getIncludeDeviceName(),
+ data.getIncludeTxPowerLevel(),
+ data.getManufacturerSpecificData(),
+ data.getServiceData(),
+ data.getServiceUuids());
+ } else if (data != null) {
+ mPeriodicAdvertisingData.includeDeviceName = data.getIncludeDeviceName();
+ mPeriodicAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
+ mPeriodicAdvertisingData.manufacturerData = data.getManufacturerSpecificData();
+ mPeriodicAdvertisingData.serviceData = data.getServiceData();
+ mPeriodicAdvertisingData.serviceUuids = data.getServiceUuids();
+ }
+ }
+
+ void onPeriodicAdvertiseEnabled(boolean enable) {
+ mPeriodicAdvertisingEnabled = enable;
+ }
+
+ void setId(int id) {
+ this.mId = id;
+ }
+
+ private static String printByteArrayInHex(byte[] data) {
+ final StringBuilder hex = new StringBuilder();
+ for (byte b : data) {
+ hex.append(String.format("%02x", b));
+ }
+ return hex.toString();
+ }
+
+ private static void dumpAppAdvertiserData(StringBuilder sb, AppAdvertiserData advData) {
+ sb.append("\n └Include Device Name : "
+ + advData.includeDeviceName);
+ sb.append("\n └Include Tx Power Level : "
+ + advData.includeTxPowerLevel);
+
+ if (advData.manufacturerData.size() > 0) {
+ sb.append("\n └Manufacturer Data (length of data) : "
+ + advData.manufacturerData.size());
+ }
+
+ if (!advData.serviceData.isEmpty()) {
+ sb.append("\n └Service Data(UUID, length of data) : ");
+ for (ParcelUuid uuid : advData.serviceData.keySet()) {
+ sb.append("\n [" + uuid.toString().substring(0, UUID_STRING_FILTER_LEN)
+ + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx, "
+ + advData.serviceData.get(uuid).length + "]");
+ }
+ }
+
+ if (!advData.serviceUuids.isEmpty()) {
+ sb.append("\n └Service Uuids : \n "
+ + advData.serviceUuids.toString().substring(0, UUID_STRING_FILTER_LEN)
+ + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
+ }
+ }
+
+ private static String dumpPhyString(int phy) {
+ if (phy > PHY_LE_STRINGS.length) {
+ return Integer.toString(phy);
+ } else {
+ return PHY_LE_STRINGS[phy - 1];
+ }
+ }
+
+ private static void dumpAppAdvertiseStats(StringBuilder sb, AppAdvertiseStats stats) {
+ sb.append("\n └Advertising:");
+ sb.append("\n └Interval(0.625ms) : "
+ + stats.mInterval);
+ sb.append("\n └TX POWER(dbm) : "
+ + stats.mTxPowerLevel);
+ sb.append("\n └Primary Phy : "
+ + dumpPhyString(stats.mPrimaryPhy));
+ sb.append("\n └Secondary Phy : "
+ + dumpPhyString(stats.mSecondaryPhy));
+ sb.append("\n └Legacy : "
+ + stats.mLegacy);
+ sb.append("\n └Anonymous : "
+ + stats.mAnonymous);
+ sb.append("\n └Connectable : "
+ + stats.mConnectable);
+ sb.append("\n └Scannable : "
+ + stats.mScannable);
+
+ if (stats.mAdvertisingData != null) {
+ sb.append("\n └Advertise Data:");
+ dumpAppAdvertiserData(sb, stats.mAdvertisingData);
+ }
+
+ if (stats.mScanResponseData != null) {
+ sb.append("\n └Scan Response:");
+ dumpAppAdvertiserData(sb, stats.mScanResponseData);
+ }
+
+ if (stats.mPeriodicInterval > 0) {
+ sb.append("\n └Periodic Advertising Enabled : "
+ + stats.mPeriodicAdvertisingEnabled);
+ sb.append("\n └Periodic Include TxPower : "
+ + stats.mPeriodicIncludeTxPower);
+ sb.append("\n └Periodic Interval(1.25ms) : "
+ + stats.mPeriodicInterval);
+ }
+
+ if (stats.mPeriodicAdvertisingData != null) {
+ sb.append("\n └Periodic Advertise Data:");
+ dumpAppAdvertiserData(sb, stats.mPeriodicAdvertisingData);
+ }
+
+ sb.append("\n");
+ }
+
+ static void dumpToString(StringBuilder sb, AppAdvertiseStats stats) {
+ Instant currentTime = Instant.now();
+
+ sb.append("\n " + stats.mAppName);
+ sb.append("\n Advertising ID : "
+ + stats.mId);
+ for (int i = 0; i < stats.mAdvertiserRecords.size(); i++) {
+ AppAdvertiserRecord record = stats.mAdvertiserRecords.get(i);
+
+ sb.append("\n " + (i + 1) + ":");
+ sb.append("\n └Start time : "
+ + sDateFormat.format(record.startTime));
+ if (record.stopTime == null) {
+ Duration timeElapsed = Duration.between(record.startTime, currentTime);
+ sb.append("\n └Elapsed time : "
+ + timeElapsed.toMillis() + "ms");
+ } else {
+ sb.append("\n └Stop time : "
+ + sDateFormat.format(record.stopTime));
+ }
+ sb.append("\n └Duration(10ms unit) : "
+ + record.duration);
+ sb.append("\n └Maximum number of extended advertising events : "
+ + record.maxExtendedAdvertisingEvents);
+ }
+
+ dumpAppAdvertiseStats(sb, stats);
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/gatt/AppScanStats.java b/android/app/src/com/android/bluetooth/gatt/AppScanStats.java
index cdd11f3c91d87cbdf9e14093d097cdd79b0bf8e4..b6de69d1b881a510505c87e3a524d1d41cd2bdbc 100644
--- a/android/app/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/android/app/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -105,29 +105,6 @@ import java.util.Objects;
this.filterString = "";
}
}
-
- static int getNumScanDurationsKept() {
- return AdapterService.getScanQuotaCount();
- }
-
- // 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 long getExcessiveScanningPeriodMillis() {
- return AdapterService.getScanQuotaWindowMillis();
- }
-
- // Maximum msec before scan gets downgraded to opportunistic
- static long getScanTimeoutMillis() {
- return AdapterService.getScanTimeoutMillis();
- }
-
- // Scan mode upgrade duration after scanStart()
- static long getScanUpgradeDurationMillis() {
- return AdapterService.getAdapterService().getScanUpgradeDurationMillis();
- }
-
public String appName;
public WorkSource mWorkSource; // Used for BatteryStatsManager
public final WorkSourceUtil mWorkSourceUtil; // Used for BluetoothStatsLog
@@ -186,14 +163,22 @@ import java.util.Objects;
results++;
}
- boolean isScanning() {
+ synchronized boolean isScanning() {
return !mOngoingScans.isEmpty();
}
- LastScan getScanFromScannerId(int scannerId) {
+ synchronized LastScan getScanFromScannerId(int scannerId) {
return mOngoingScans.get(scannerId);
}
+ synchronized boolean isScanTimeout(int scannerId) {
+ LastScan onGoingScan = getScanFromScannerId(scannerId);
+ if (onGoingScan == null) {
+ return false;
+ }
+ return onGoingScan.isTimeout;
+ }
+
synchronized void recordScanStart(ScanSettings settings, List filters,
boolean isFilterScan, boolean isCallbackScan, int scannerId) {
LastScan existingScan = getScanFromScannerId(scannerId);
diff --git a/android/app/src/com/android/bluetooth/gatt/CallbackInfo.java b/android/app/src/com/android/bluetooth/gatt/CallbackInfo.java
index f6035b392a5c0d9f1e0251f119615c7072414cac..ab3fe4ea4b8b32e6efcc0703637772410b227a6e 100644
--- a/android/app/src/com/android/bluetooth/gatt/CallbackInfo.java
+++ b/android/app/src/com/android/bluetooth/gatt/CallbackInfo.java
@@ -20,9 +20,7 @@ package com.android.bluetooth.gatt;
* These are held during congestion and reported when congestion clears.
* @hide
*/
-/*package*/
-
-class CallbackInfo {
+/* package */ class CallbackInfo {
public String address;
public int status;
public int handle;
@@ -58,6 +56,6 @@ class CallbackInfo {
this.address = address;
this.status = status;
this.handle = handle;
+ this.value = value;
}
}
-
diff --git a/android/app/src/com/android/bluetooth/gatt/ContextMap.java b/android/app/src/com/android/bluetooth/gatt/ContextMap.java
index ed95301ad6756e6c45c3bf4e73f2bcd1239c16f3..dc9fe866aec4b887834293f0fe96704b9435c162 100644
--- a/android/app/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/android/app/src/com/android/bluetooth/gatt/ContextMap.java
@@ -15,6 +15,9 @@
*/
package com.android.bluetooth.gatt;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
@@ -24,6 +27,13 @@ import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+import com.android.internal.annotations.GuardedBy;
+
+import com.google.common.collect.EvictingQueue;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -39,7 +49,8 @@ import java.util.UUID;
* This class manages application callbacks and keeps track of GATT connections.
* @hide
*/
-/*package*/ class ContextMap {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+public class ContextMap {
private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
/**
@@ -125,6 +136,15 @@ import java.util.UUID;
this.appScanStats = appScanStats;
}
+ /**
+ * Creates a new app context for advertiser.
+ */
+ App(int id, C callback, String name) {
+ this.id = id;
+ this.callback = callback;
+ this.name = name;
+ }
+
/**
* Link death recipient
*/
@@ -169,11 +189,22 @@ import java.util.UUID;
}
/** Our internal application list */
+ private final Object mAppsLock = new Object();
+ @GuardedBy("mAppsLock")
private List mApps = new ArrayList();
/** Internal map to keep track of logging information by app name */
private HashMap mAppScanStats = new HashMap();
+ /** Internal map to keep track of logging information by advertise id */
+ private final Map mAppAdvertiseStats =
+ new HashMap();
+
+ private static final int ADVERTISE_STATE_MAX_SIZE = 5;
+
+ private final EvictingQueue mLastAdvertises =
+ EvictingQueue.create(ADVERTISE_STATE_MAX_SIZE);
+
/** Internal list of connected devices **/
private Set mConnections = new HashSet();
@@ -200,6 +231,34 @@ import java.util.UUID;
}
}
+ /**
+ * Add an entry to the application context list for advertiser.
+ */
+ App add(int id, C callback, GattService service) {
+ int appUid = Binder.getCallingUid();
+ String appName = service.getPackageManager().getNameForUid(appUid);
+ if (appName == null) {
+ // Assign an app name if one isn't found
+ appName = "Unknown App (UID: " + appUid + ")";
+ }
+
+ synchronized (mAppsLock) {
+ synchronized (this) {
+ if (!mAppAdvertiseStats.containsKey(id)) {
+ AppAdvertiseStats appAdvertiseStats = BluetoothMethodProxy.getInstance()
+ .createAppAdvertiseStats(appUid, id, appName, this, service);
+ mAppAdvertiseStats.put(id, appAdvertiseStats);
+ }
+ }
+ App app = getById(appUid);
+ if (app == null) {
+ app = new App(appUid, callback, appName);
+ mApps.add(app);
+ }
+ return app;
+ }
+ }
+
/**
* Remove the context for a given UUID
*/
@@ -382,6 +441,135 @@ import java.util.UUID;
return mAppScanStats.get(uid);
}
+ /**
+ * Remove the context for a given application ID.
+ */
+ void removeAppAdvertiseStats(int id) {
+ synchronized (this) {
+ mAppAdvertiseStats.remove(id);
+ }
+ }
+
+ /**
+ * Get Logging info by ID
+ */
+ AppAdvertiseStats getAppAdvertiseStatsById(int id) {
+ synchronized (this) {
+ return mAppAdvertiseStats.get(id);
+ }
+ }
+
+ /**
+ * update the advertiser ID by the regiseter ID
+ */
+ void setAdvertiserIdByRegId(int regId, int advertiserId) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(regId);
+ if (stats == null) {
+ return;
+ }
+ stats.setId(advertiserId);
+ mAppAdvertiseStats.remove(regId);
+ mAppAdvertiseStats.put(advertiserId, stats);
+ }
+ }
+
+ void recordAdvertiseStart(int id, AdvertisingSetParameters parameters,
+ AdvertiseData advertiseData, AdvertiseData scanResponse,
+ PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
+ int duration, int maxExtAdvEvents) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.recordAdvertiseStart(parameters, advertiseData, scanResponse,
+ periodicParameters, periodicData, duration, maxExtAdvEvents);
+ }
+ }
+
+ void recordAdvertiseStop(int id) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.recordAdvertiseStop();
+ mAppAdvertiseStats.remove(id);
+ mLastAdvertises.add(stats);
+ }
+ }
+
+ void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents);
+ }
+ }
+
+ void setAdvertisingData(int id, AdvertiseData data) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.setAdvertisingData(data);
+ }
+ }
+
+ void setScanResponseData(int id, AdvertiseData data) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.setScanResponseData(data);
+ }
+ }
+
+ void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.setAdvertisingParameters(parameters);
+ }
+ }
+
+ void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.setPeriodicAdvertisingParameters(parameters);
+ }
+ }
+
+ void setPeriodicAdvertisingData(int id, AdvertiseData data) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.setPeriodicAdvertisingData(data);
+ }
+ }
+
+ void onPeriodicAdvertiseEnabled(int id, boolean enable) {
+ synchronized (this) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
+ if (stats == null) {
+ return;
+ }
+ stats.onPeriodicAdvertiseEnabled(enable);
+ }
+ }
+
/**
* Get the device addresses for all connected devices
*/
@@ -477,7 +665,9 @@ import java.util.UUID;
while (i.hasNext()) {
App entry = i.next();
entry.unlinkToDeath();
- entry.appScanStats.isRegistered = false;
+ if (entry.appScanStats != null) {
+ entry.appScanStats.isRegistered = false;
+ }
i.remove();
}
}
@@ -485,6 +675,11 @@ import java.util.UUID;
synchronized (mConnections) {
mConnections.clear();
}
+
+ synchronized (this) {
+ mAppAdvertiseStats.clear();
+ mLastAdvertises.clear();
+ }
}
/**
@@ -514,4 +709,31 @@ import java.util.UUID;
appScanStats.dumpToString(sb);
}
}
+
+ /**
+ * Logs advertiser debug information.
+ */
+ void dumpAdvertiser(StringBuilder sb) {
+ synchronized (this) {
+ if (!mLastAdvertises.isEmpty()) {
+ sb.append("\n last " + mLastAdvertises.size() + " advertising:");
+ for (AppAdvertiseStats stats : mLastAdvertises) {
+ AppAdvertiseStats.dumpToString(sb, stats);
+ }
+ sb.append("\n");
+ }
+
+ if (!mAppAdvertiseStats.isEmpty()) {
+ sb.append(" Total number of ongoing advertising : "
+ + mAppAdvertiseStats.size());
+ sb.append("\n Ongoing advertising:");
+ for (Integer key : mAppAdvertiseStats.keySet()) {
+ AppAdvertiseStats stats = mAppAdvertiseStats.get(key);
+ AppAdvertiseStats.dumpToString(sb, stats);
+ }
+ }
+ sb.append("\n");
+ }
+ Log.d(TAG, sb.toString());
+ }
}
diff --git a/android/app/src/com/android/bluetooth/gatt/GattDebugUtils.java b/android/app/src/com/android/bluetooth/gatt/GattDebugUtils.java
index 232477b9b163adedbde6c3ed6b7aaeda9de0962d..0a1bfb35812f6425506b7e1bfa5e1267fb12dea4 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattDebugUtils.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -20,6 +20,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.UUID;
/**
@@ -29,17 +32,23 @@ import java.util.UUID;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "DebugUtils";
private static final boolean DEBUG_ADMIN = GattServiceConfig.DEBUG_ADMIN;
- private static final String ACTION_GATT_PAIRING_CONFIG =
+ @VisibleForTesting
+ static final String ACTION_GATT_PAIRING_CONFIG =
"android.bluetooth.action.GATT_PAIRING_CONFIG";
- private static final String ACTION_GATT_TEST_USAGE = "android.bluetooth.action.GATT_TEST_USAGE";
- private static final String ACTION_GATT_TEST_ENABLE =
+ @VisibleForTesting
+ static final String ACTION_GATT_TEST_USAGE = "android.bluetooth.action.GATT_TEST_USAGE";
+ @VisibleForTesting
+ static final String ACTION_GATT_TEST_ENABLE =
"android.bluetooth.action.GATT_TEST_ENABLE";
- private static final String ACTION_GATT_TEST_CONNECT =
+ @VisibleForTesting
+ static final String ACTION_GATT_TEST_CONNECT =
"android.bluetooth.action.GATT_TEST_CONNECT";
- private static final String ACTION_GATT_TEST_DISCONNECT =
+ @VisibleForTesting
+ static final String ACTION_GATT_TEST_DISCONNECT =
"android.bluetooth.action.GATT_TEST_DISCONNECT";
- private static final String ACTION_GATT_TEST_DISCOVER =
+ @VisibleForTesting
+ static final String ACTION_GATT_TEST_DISCOVER =
"android.bluetooth.action.GATT_TEST_DISCOVER";
private static final String EXTRA_ENABLE = "enable";
@@ -69,7 +78,7 @@ import java.util.UUID;
* import com.android.bluetooth.gatt.GattService;
*/
static boolean handleDebugAction(GattService svc, Intent intent) {
- if (!DEBUG_ADMIN) {
+ if (!DEBUG_ADMIN && !Utils.isInstrumentationTestMode()) {
return false;
}
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 009315d6346adbc2a8c19d9b76a31a3ae7b8421d..2bcedd72c95e4d0eda02aba24c8fa401cfa51c60 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -24,6 +24,7 @@ import android.annotation.SuppressLint;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.Context;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -52,6 +53,8 @@ import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.content.AttributionSource;
import android.content.Intent;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Build;
@@ -75,6 +78,7 @@ import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.BluetoothAdapterProxy;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -220,6 +224,13 @@ public class GattService extends ProfileService {
ScannerMap mScannerMap = new ScannerMap();
+ /**
+ * List of our registered advertisers.
+ */
+ static class AdvertiserMap extends ContextMap {}
+
+ private AdvertiserMap mAdvertiserMap = new AdvertiserMap();
+
/**
* List of our registered clients.
*/
@@ -257,11 +268,12 @@ public class GattService extends ProfileService {
/**
* HashMap used to synchronize writeCharacteristic calls mapping remote device address to
- * available permit (either 1 or 0).
+ * available permit (connectId or -1).
*/
- private final HashMap mPermits = new HashMap<>();
+ private final HashMap mPermits = new HashMap<>();
private AdapterService mAdapterService;
+ private BluetoothAdapterProxy mBluetoothAdapterProxy;
private AdvertiseManager mAdvertiseManager;
private PeriodicScanManager mPeriodicScanManager;
private ScanManager mScanManager;
@@ -319,12 +331,13 @@ public class GattService extends ProfileService {
initializeNative();
mAdapterService = AdapterService.getAdapterService();
+ mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance();
mCompanionManager = getSystemService(CompanionDeviceManager.class);
mAppOps = getSystemService(AppOpsManager.class);
- mAdvertiseManager = new AdvertiseManager(this, mAdapterService);
+ mAdvertiseManager = new AdvertiseManager(this, mAdapterService, mAdvertiserMap);
mAdvertiseManager.start();
- mScanManager = new ScanManager(this, mAdapterService);
+ mScanManager = new ScanManager(this, mAdapterService, mBluetoothAdapterProxy);
mScanManager.start();
mPeriodicScanManager = new PeriodicScanManager(mAdapterService);
@@ -341,6 +354,7 @@ public class GattService extends ProfileService {
}
setGattService(null);
mScannerMap.clear();
+ mAdvertiserMap.clear();
mClientMap.clear();
mServerMap.clear();
mHandleMap.clear();
@@ -426,6 +440,15 @@ public class GattService extends ProfileService {
return sGattService;
}
+ @VisibleForTesting
+ ScanManager getScanManager() {
+ if (mScanManager == null) {
+ Log.w(TAG, "getScanManager(): scan manager is null");
+ return null;
+ }
+ return mScanManager;
+ }
+
private static synchronized void setGattService(GattService instance) {
if (DBG) {
Log.d(TAG, "setGattService(): set to: " + instance);
@@ -549,7 +572,8 @@ public class GattService extends ProfileService {
/**
* Handlers for incoming service calls
*/
- private static class BluetoothGattBinder extends IBluetoothGatt.Stub
+ @VisibleForTesting
+ static class BluetoothGattBinder extends IBluetoothGatt.Stub
implements IProfileServiceBinder {
private GattService mService;
@@ -2013,7 +2037,7 @@ public class GattService extends ProfileService {
synchronized (mPermits) {
Log.d(TAG, "onConnected() - adding permit for address="
+ address);
- mPermits.putIfAbsent(address, new AtomicBoolean(true));
+ mPermits.putIfAbsent(address, -1);
}
connectionState = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
@@ -2045,6 +2069,13 @@ public class GattService extends ProfileService {
+ address);
mPermits.remove(address);
}
+ } else {
+ synchronized (mPermits) {
+ if (mPermits.get(address) == connId) {
+ Log.d(TAG, "onDisconnected() - set permit -1 for address=" + address);
+ mPermits.put(address, -1);
+ }
+ }
}
if (app != null) {
@@ -2350,7 +2381,7 @@ public class GattService extends ProfileService {
synchronized (mPermits) {
Log.d(TAG, "onWriteCharacteristic() - increasing permit for address="
+ address);
- mPermits.get(address).set(true);
+ mPermits.put(address, -1);
}
if (VDBG) {
@@ -3397,7 +3428,7 @@ public class GattService extends ProfileService {
Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect
+ ", opportunistic=" + opportunistic + ", phy=" + phy);
}
- statsLogAppPackage(address, attributionSource.getPackageName());
+ statsLogAppPackage(address, attributionSource.getUid(), clientIf);
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
BluetoothProtoEnums.CONNECTION_STATE_CONNECTING);
@@ -3643,18 +3674,18 @@ public class GattService extends ProfileService {
Log.d(TAG, "writeCharacteristic() - trying to acquire permit.");
// Lock the thread until onCharacteristicWrite callback comes back.
synchronized (mPermits) {
- AtomicBoolean atomicBoolean = mPermits.get(address);
- if (atomicBoolean == null) {
+ Integer permit = mPermits.get(address);
+ if (permit == null) {
Log.d(TAG, "writeCharacteristic() - atomicBoolean uninitialized!");
return BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED;
}
- boolean success = atomicBoolean.get();
+ boolean success = (permit == -1);
if (!success) {
Log.d(TAG, "writeCharacteristic() - no permit available.");
return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
}
- atomicBoolean.set(false);
+ mPermits.put(address, connId);
}
gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
@@ -3988,8 +4019,17 @@ public class GattService extends ProfileService {
connectionState = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
}
+ int applicationUid = -1;
+
+ try {
+ applicationUid = this.getPackageManager().getPackageUid(app.name, PackageInfoFlags.of(0));
+
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "onClientConnected() uid_not_found=" + app.name);
+ }
+
app.callback.onServerConnectionState((byte) 0, serverIf, connected, address);
- statsLogAppPackage(address, app.name);
+ statsLogAppPackage(address, applicationUid, serverIf);
statsLogGattConnectionStateChange(
BluetoothProfile.GATT_SERVER, address, serverIf, connectionState);
}
@@ -4646,6 +4686,9 @@ public class GattService extends ProfileService {
sb.append("GATT Scanner Map\n");
mScannerMap.dump(sb);
+ sb.append("GATT Advertiser Map\n");
+ mAdvertiserMap.dumpAdvertiser(sb);
+
sb.append("GATT Client Map\n");
mClientMap.dump(sb);
@@ -4665,14 +4708,14 @@ public class GattService extends ProfileService {
}
}
- private void statsLogAppPackage(String address, String app) {
+ private void statsLogAppPackage(String address, int applicationUid, int sessionIndex) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
BluetoothStatsLog.write(
- BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED,
- mAdapterService.getMetricId(device), app);
+ BluetoothStatsLog.BLUETOOTH_GATT_APP_INFO,
+ sessionIndex, mAdapterService.getMetricId(device), applicationUid);
if (DBG) {
Log.d(TAG, "Gatt Logging: metric_id=" + mAdapterService.getMetricId(device)
- + ", app=" + app);
+ + ", app_uid=" + applicationUid);
}
}
diff --git a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 8231519e397a5225a2b410ec318be1d76406f9ec..9919732af0bf29053781ac0eafb129778cc4a93a 100644
--- a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -39,6 +39,7 @@ import java.util.UUID;
public static final int TYPE_LOCAL_NAME = 4;
public static final int TYPE_MANUFACTURER_DATA = 5;
public static final int TYPE_SERVICE_DATA = 6;
+ public static final int TYPE_ADVERTISING_DATA_TYPE = 7;
// Max length is 31 - 3(flags) - 2 (one byte for length and one byte for type).
private static final int MAX_LEN_PER_FIELD = 26;
@@ -56,6 +57,7 @@ import java.util.UUID;
public String name;
public int company;
public int company_mask;
+ public int ad_type;
public byte[] data;
public byte[] data_mask;
}
@@ -145,6 +147,15 @@ import java.util.UUID;
mEntries.add(entry);
}
+ void addAdvertisingDataType(int adType, byte[] data, byte[] dataMask) {
+ Entry entry = new Entry();
+ entry.type = TYPE_ADVERTISING_DATA_TYPE;
+ entry.ad_type = adType;
+ entry.data = data;
+ entry.data_mask = dataMask;
+ mEntries.add(entry);
+ }
+
Entry pop() {
if (mEntries.isEmpty()) {
return null;
@@ -226,6 +237,10 @@ import java.util.UUID;
addServiceData(serviceData, serviceDataMask);
}
}
+ if (filter.getAdvertisingDataType() > 0) {
+ addAdvertisingDataType(filter.getAdvertisingDataType(),
+ filter.getAdvertisingData(), filter.getAdvertisingDataMask());
+ }
}
private byte[] concate(ParcelUuid serviceDataUuid, byte[] serviceData) {
diff --git a/android/app/src/com/android/bluetooth/gatt/ScanManager.java b/android/app/src/com/android/bluetooth/gatt/ScanManager.java
index 84033cd98071fd157fd73168ce2a72103ecd5ce3..fe506cae5ac374f1d6997c9ea762e8587c3a5f8b 100644
--- a/android/app/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/ScanManager.java
@@ -20,7 +20,6 @@ import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
@@ -45,6 +44,8 @@ import android.view.Display;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.BluetoothAdapterProxy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayDeque;
import java.util.Collections;
@@ -86,17 +87,18 @@ public class ScanManager {
static final int SCAN_RESULT_TYPE_FULL = 2;
static final int SCAN_RESULT_TYPE_BOTH = 3;
- // Internal messages for handling BLE scan operations.
- 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;
- private static final int MSG_SUSPEND_SCANS = 4;
- private static final int MSG_RESUME_SCANS = 5;
- private static final int MSG_IMPORTANCE_CHANGE = 6;
- private static final int MSG_SCREEN_ON = 7;
- private static final int MSG_SCREEN_OFF = 8;
- private static final int MSG_REVERT_SCAN_MODE_UPGRADE = 9;
+ // Messages for handling BLE scan operations.
+ @VisibleForTesting
+ static final int MSG_START_BLE_SCAN = 0;
+ static final int MSG_STOP_BLE_SCAN = 1;
+ static final int MSG_FLUSH_BATCH_RESULTS = 2;
+ static final int MSG_SCAN_TIMEOUT = 3;
+ static final int MSG_SUSPEND_SCANS = 4;
+ static final int MSG_RESUME_SCANS = 5;
+ static final int MSG_IMPORTANCE_CHANGE = 6;
+ static final int MSG_SCREEN_ON = 7;
+ static final int MSG_SCREEN_OFF = 8;
+ static final int MSG_REVERT_SCAN_MODE_UPGRADE = 9;
private static final String ACTION_REFRESH_BATCHED_SCAN =
"com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
@@ -115,6 +117,7 @@ public class ScanManager {
private boolean mBatchAlarmReceiverRegistered;
private ScanNative mScanNative;
private volatile ClientHandler mHandler;
+ private BluetoothAdapterProxy mBluetoothAdapterProxy;
private Set mRegularScanClients;
private Set mBatchClients;
@@ -134,7 +137,8 @@ public class ScanManager {
private final SparseBooleanArray mIsUidForegroundMap = new SparseBooleanArray();
private boolean mScreenOn = false;
- private class UidImportance {
+ @VisibleForTesting
+ static class UidImportance {
public int uid;
public int importance;
@@ -144,7 +148,8 @@ public class ScanManager {
}
}
- ScanManager(GattService service, AdapterService adapterService) {
+ ScanManager(GattService service, AdapterService adapterService,
+ BluetoothAdapterProxy bluetoothAdapterProxy) {
mRegularScanClients =
Collections.newSetFromMap(new ConcurrentHashMap());
mBatchClients = Collections.newSetFromMap(new ConcurrentHashMap());
@@ -157,6 +162,7 @@ public class ScanManager {
mActivityManager = mService.getSystemService(ActivityManager.class);
mLocationManager = mService.getSystemService(LocationManager.class);
mAdapterService = adapterService;
+ mBluetoothAdapterProxy = bluetoothAdapterProxy;
mPriorityMap.put(ScanSettings.SCAN_MODE_OPPORTUNISTIC, 0);
mPriorityMap.put(ScanSettings.SCAN_MODE_SCREEN_OFF, 1);
@@ -175,6 +181,7 @@ public class ScanManager {
if (mDm != null) {
mDm.registerDisplayListener(mDisplayListener, null);
}
+ mScreenOn = isScreenOn();
if (mActivityManager != null) {
mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
FOREGROUND_IMPORTANCE_CUTOFF);
@@ -234,6 +241,13 @@ public class ScanManager {
return mRegularScanClients;
}
+ /**
+ * Returns the suspended scan queue.
+ */
+ Set getSuspendedScanQueue() {
+ return mSuspendedScanClients;
+ }
+
/**
* Returns batch scan queue.
*/
@@ -268,6 +282,9 @@ public class ScanManager {
if (client == null) {
client = mScanNative.getRegularScanClient(scannerId);
}
+ if (client == null) {
+ client = mScanNative.getSuspendedScanClient(scannerId);
+ }
sendMessage(MSG_STOP_BLE_SCAN, client);
}
@@ -298,8 +315,11 @@ public class ScanManager {
}
private boolean isFilteringSupported() {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- return adapter.isOffloadedFilteringSupported();
+ if (mBluetoothAdapterProxy == null) {
+ Log.e(TAG, "mBluetoothAdapterProxy is null");
+ return false;
+ }
+ return mBluetoothAdapterProxy.isOffloadedScanFilteringSupported();
}
// Handler class that handles BLE scan operations.
@@ -363,7 +383,7 @@ public class ScanManager {
return;
}
- if (requiresScreenOn(client) && !isScreenOn()) {
+ if (requiresScreenOn(client) && !mScreenOn) {
Log.w(TAG, "Cannot start unfiltered scan in screen-off. This scan will be resumed "
+ "later: " + client.scannerId);
mSuspendedScanClients.add(client);
@@ -400,6 +420,11 @@ public class ScanManager {
msg.obj = client;
// Only one timeout message should exist at any time
sendMessageDelayed(msg, mAdapterService.getScanTimeoutMillis());
+ if (DBG) {
+ Log.d(TAG,
+ "apply scan timeout (" + mAdapterService.getScanTimeoutMillis()
+ + ")" + "to scannerId " + client.scannerId);
+ }
}
}
}
@@ -429,13 +454,10 @@ public class ScanManager {
mSuspendedScanClients.remove(client);
}
removeMessages(MSG_REVERT_SCAN_MODE_UPGRADE, client);
+ removeMessages(MSG_SCAN_TIMEOUT, client);
if (mRegularScanClients.contains(client)) {
mScanNative.stopRegularScan(client);
- if (mScanNative.numRegularScanClients() == 0) {
- removeMessages(MSG_SCAN_TIMEOUT);
- }
-
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
}
@@ -493,7 +515,7 @@ public class ScanManager {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
void handleSuspendScans() {
for (ScanClient client : mRegularScanClients) {
- if ((requiresScreenOn(client) && !isScreenOn())
+ if ((requiresScreenOn(client) && !mScreenOn)
|| (requiresLocationOn(client) && !mLocationManager.isLocationEnabled())) {
/*Suspend unfiltered scans*/
if (client.stats != null) {
@@ -524,6 +546,9 @@ public class ScanManager {
}
private boolean updateScanModeScreenOff(ScanClient client) {
+ if (mScanNative.isTimeoutScanClient(client)) {
+ return false;
+ }
if (!isAppForeground(client) && !mScanNative.isOpportunisticScanClient(client)) {
return client.updateScanMode(ScanSettings.SCAN_MODE_SCREEN_OFF);
}
@@ -555,7 +580,7 @@ public class ScanManager {
if (upgradeScanModeBeforeStart(client)) {
return true;
}
- if (isScreenOn()) {
+ if (mScreenOn) {
return updateScanModeScreenOn(client);
} else {
return updateScanModeScreenOff(client);
@@ -613,6 +638,10 @@ public class ScanManager {
}
private boolean updateScanModeScreenOn(ScanClient client) {
+ if (mScanNative.isTimeoutScanClient(client)) {
+ return false;
+ }
+
int newScanMode = (isAppForeground(client)
|| mScanNative.isOpportunisticScanClient(client))
? client.scanModeApp : SCAN_MODE_APP_IN_BACKGROUND;
@@ -633,7 +662,7 @@ public class ScanManager {
void handleResumeScans() {
for (ScanClient client : mSuspendedScanClients) {
- if ((!requiresScreenOn(client) || isScreenOn())
+ if ((!requiresScreenOn(client) || mScreenOn)
&& (!requiresLocationOn(client) || mLocationManager.isLocationEnabled())) {
if (client.stats != null) {
client.stats.recordScanResume(client.scannerId);
@@ -871,14 +900,17 @@ public class ScanManager {
}
private boolean isExemptFromScanDowngrade(ScanClient client) {
- return isOpportunisticScanClient(client) || isFirstMatchScanClient(client)
- || !shouldUseAllPassFilter(client);
+ return isOpportunisticScanClient(client) || isFirstMatchScanClient(client);
}
private boolean isOpportunisticScanClient(ScanClient client) {
return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
}
+ private boolean isTimeoutScanClient(ScanClient client) {
+ return (client.stats != null) && client.stats.isScanTimeout(client.scannerId);
+ }
+
private boolean isFirstMatchScanClient(ScanClient client) {
return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
!= 0;
@@ -1046,11 +1078,23 @@ public class ScanManager {
void regularScanTimeout(ScanClient client) {
if (!isExemptFromScanDowngrade(client) && client.stats.isScanningTooLong()) {
- Log.w(TAG,
- "Moving scan client to opportunistic (scannerId " + client.scannerId + ")");
- setOpportunisticScanClient(client);
- removeScanFilters(client.scannerId);
- client.stats.setScanTimeout(client.scannerId);
+ if (DBG) {
+ Log.d(TAG, "regularScanTimeout - client scan time was too long");
+ }
+ if (client.filters == null || client.filters.isEmpty()) {
+ Log.w(TAG,
+ "Moving unfiltered scan client to opportunistic scan (scannerId "
+ + client.scannerId + ")");
+ setOpportunisticScanClient(client);
+ removeScanFilters(client.scannerId);
+ client.stats.setScanTimeout(client.scannerId);
+ } else {
+ Log.w(TAG,
+ "Moving filtered scan client to downgraded scan (scannerId "
+ + client.scannerId + ")");
+ client.updateScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
+ client.stats.setScanTimeout(client.scannerId);
+ }
}
// The scan should continue for background scans
@@ -1086,6 +1130,15 @@ public class ScanManager {
return null;
}
+ ScanClient getSuspendedScanClient(int scannerId) {
+ for (ScanClient client : mSuspendedScanClients) {
+ if (client.scannerId == scannerId) {
+ return client;
+ }
+ }
+ return null;
+ }
+
void stopBatchScan(ScanClient client) {
mBatchClients.remove(client);
removeScanFilters(client.scannerId);
@@ -1284,11 +1337,12 @@ public class ScanManager {
private void initFilterIndexStack() {
int maxFiltersSupported =
AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
- // Start from index 3 as:
+ // Start from index 4 as:
// index 0 is reserved for ALL_PASS filter in Settings app.
// index 1 is reserved for ALL_PASS filter for regular scan apps.
// index 2 is reserved for ALL_PASS filter for batch scan apps.
- for (int i = 3; i < maxFiltersSupported; ++i) {
+ // index 3 is reserved for BAP/CAP Announcements
+ for (int i = 4; i < maxFiltersSupported; ++i) {
mFilterIndexStack.add(i);
}
}
@@ -1527,6 +1581,11 @@ public class ScanManager {
private native void gattClientReadScanReportsNative(int clientIf, int scanType);
}
+ @VisibleForTesting
+ ClientHandler getClientHandler() {
+ return mHandler;
+ }
+
private boolean isScreenOn() {
Display[] displays = mDm.getDisplays();
@@ -1605,7 +1664,7 @@ public class ScanManager {
}
for (ScanClient client : mRegularScanClients) {
- if (client.appUid != uid) {
+ if (client.appUid != uid || mScanNative.isTimeoutScanClient(client)) {
continue;
}
if (isForeground) {
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientService.java b/android/app/src/com/android/bluetooth/hap/HapClientService.java
index 756d72b4a2de1fedbcd9178b3b84cd2bd95d4aff..2b06aa608e1709068c0e0795c6542e57dfe74951 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientService.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientService.java
@@ -100,7 +100,8 @@ public class HapClientService extends ProfileService {
return BluetoothProperties.isProfileHapClientEnabled().orElse(false);
}
- private static synchronized void setHapClient(HapClientService instance) {
+ @VisibleForTesting
+ static synchronized void setHapClient(HapClientService instance) {
if (DBG) {
Log.d(TAG, "setHapClient(): set to: " + instance);
}
@@ -267,6 +268,8 @@ public class HapClientService extends ProfileService {
return;
}
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnecting device because it was unbonded.");
+ disconnect(device);
return;
}
removeStateMachine(device);
@@ -696,6 +699,12 @@ public class HapClientService extends ProfileService {
BluetoothHapPresetInfo defaultValue = null;
if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return defaultValue;
+ if (Utils.isPtsTestMode()) {
+ /* We want native to be called for PTS testing even we have all
+ * the data in the cache here
+ */
+ mHapClientNativeInterface.getPresetInfo(device, presetIndex);
+ }
List current_presets = mPresetsMap.get(device);
if (current_presets != null) {
for (BluetoothHapPresetInfo preset : current_presets) {
@@ -1203,6 +1212,8 @@ public class HapClientService extends ProfileService {
@VisibleForTesting
static class BluetoothHapClientBinder extends IBluetoothHapClient.Stub
implements IProfileServiceBinder {
+ @VisibleForTesting
+ boolean mIsTesting = false;
private HapClientService mService;
BluetoothHapClientBinder(HapClientService svc) {
@@ -1210,8 +1221,11 @@ public class HapClientService extends ProfileService {
}
private HapClientService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)
+ if (mIsTesting) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
Log.w(TAG, "Hearing Access call not allowed for non-active user");
return null;
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java b/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
index e322f505f0e5ce9ada3abb8c943f2d5160eef165..59700dbdad0297654f91bfacf952ae5be47a641a 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
@@ -108,7 +108,7 @@ public class HapClientStackEvent {
private String eventTypeValueListToString(int type, List value) {
switch (type) {
case EVENT_TYPE_ON_PRESET_INFO:
- return "{presets count: " + value.size() + "}";
+ return "{presets count: " + (value == null ? 0 : value.size()) + "}";
default:
return "{list: empty}";
}
@@ -245,18 +245,6 @@ public class HapClientStackEvent {
return features_str;
}
- private String availablePresetsToString(byte[] value) {
- if (value.length == 0) return "NONE";
-
- String presets_str = "[";
- for (int i = 0; i < value.length; i++) {
- presets_str += (value[i] + ", ");
- }
-
- presets_str += "]";
- return presets_str;
- }
-
private static String eventTypeToString(int type) {
switch (type) {
case EVENT_TYPE_NONE:
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
index 62c9dd2d14fde2b7045193625f947dd16357e937..dc85f0aea3bcdbba5f86b4a176f906d841e6a952 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
@@ -73,7 +73,8 @@ final class HapClientStateMachine extends StateMachine {
static final int STACK_EVENT = 101;
private static final boolean DBG = true;
private static final String TAG = "HapClientStateMachine";
- private static final int CONNECT_TIMEOUT = 201;
+ @VisibleForTesting
+ static final int CONNECT_TIMEOUT = 201;
// NOTE: the value is not "final" - it is modified in the unit tests
@VisibleForTesting
diff --git a/android/app/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/android/app/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index 9ef2678c8b8a070fc1c8da73350032e23aef4d70..a2aabd05aacbe10856bbb4d36c90ebe95bf8c21c 100644
--- a/android/app/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -128,14 +128,16 @@ public class HearingAidNativeInterface {
return mAdapter.getRemoteDevice(address);
}
- private byte[] getByteAddress(BluetoothDevice device) {
+ @VisibleForTesting
+ byte[] getByteAddress(BluetoothDevice device) {
if (device == null) {
return Utils.getBytesFromAddress("00:00:00:00:00:00");
}
return Utils.getBytesFromAddress(device.getAddress());
}
- private void sendMessageToService(HearingAidStackEvent event) {
+ @VisibleForTesting
+ void sendMessageToService(HearingAidStackEvent event) {
HearingAidService service = HearingAidService.getHearingAidService();
if (service != null) {
service.messageFromNative(event);
@@ -148,7 +150,8 @@ public class HearingAidNativeInterface {
// All callbacks are routed via the Service which will disambiguate which
// state machine the message should be routed to.
- private void onConnectionStateChanged(int state, byte[] address) {
+ @VisibleForTesting
+ void onConnectionStateChanged(int state, byte[] address) {
HearingAidStackEvent event =
new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.device = getDevice(address);
@@ -160,7 +163,8 @@ public class HearingAidNativeInterface {
sendMessageToService(event);
}
- private void onDeviceAvailable(byte capabilities, long hiSyncId, byte[] address) {
+ @VisibleForTesting
+ void onDeviceAvailable(byte capabilities, long hiSyncId, byte[] address) {
HearingAidStackEvent event = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
event.device = getDevice(address);
diff --git a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 4aa87e17ff6d675f148fd2bda8fd7054a3dab5ee..7f51e42d4b4f1e7cd2c9464aa688c1ec72abf898 100644
--- a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -29,9 +29,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
+import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.ParcelUuid;
import android.sysprop.BluetoothProperties;
import android.util.Log;
@@ -73,6 +77,7 @@ public class HearingAidService extends ProfileService {
private DatabaseManager mDatabaseManager;
private HandlerThread mStateMachinesThread;
private BluetoothDevice mPreviousAudioDevice;
+ private BluetoothDevice mActiveDevice;
@VisibleForTesting
HearingAidNativeInterface mHearingAidNativeInterface;
@@ -88,6 +93,12 @@ public class HearingAidService extends ProfileService {
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ private final AudioManagerOnAudioDevicesAddedCallback mAudioManagerOnAudioDevicesAddedCallback =
+ new AudioManagerOnAudioDevicesAddedCallback();
+ private final AudioManagerOnAudioDevicesRemovedCallback
+ mAudioManagerOnAudioDevicesRemovedCallback =
+ new AudioManagerOnAudioDevicesRemovedCallback();
private final ServiceFactory mFactory = new ServiceFactory();
@@ -206,6 +217,9 @@ public class HearingAidService extends ProfileService {
}
}
+ mAudioManager.unregisterAudioDeviceCallback(mAudioManagerOnAudioDevicesAddedCallback);
+ mAudioManager.unregisterAudioDeviceCallback(mAudioManagerOnAudioDevicesRemovedCallback);
+
// Clear AdapterService, HearingAidNativeInterface
mAudioManager = null;
mHearingAidNativeInterface = null;
@@ -238,7 +252,8 @@ public class HearingAidService extends ProfileService {
return sHearingAidService;
}
- private static synchronized void setHearingAidService(HearingAidService instance) {
+ @VisibleForTesting
+ static synchronized void setHearingAidService(HearingAidService instance) {
if (DBG) {
Log.d(TAG, "setHearingAidService(): set to: " + instance);
}
@@ -582,6 +597,12 @@ public class HearingAidService extends ProfileService {
}
return true;
}
+
+ /* No action needed since this is the same device as previousely activated */
+ if (device.equals(mActiveDevice)) {
+ return true;
+ }
+
if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
Log.e(TAG, "setActiveDevice(" + device + "): failed because device not connected");
return false;
@@ -671,6 +692,46 @@ public class HearingAidService extends ProfileService {
}
}
+ private void notifyActiveDeviceChanged() {
+ Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mActiveDevice);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ sendBroadcast(intent, BLUETOOTH_CONNECT);
+ }
+
+ /* Notifications of audio device disconnection events. */
+ private class AudioManagerOnAudioDevicesRemovedCallback extends AudioDeviceCallback {
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ for (AudioDeviceInfo deviceInfo : removedDevices) {
+ if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
+ notifyActiveDeviceChanged();
+ if (DBG) {
+ Log.d(TAG, " onAudioDevicesRemoved: device type: " + deviceInfo.getType());
+ }
+ mAudioManager.unregisterAudioDeviceCallback(this);
+ }
+ }
+ }
+ }
+
+ /* Notifications of audio device connection events. */
+ private class AudioManagerOnAudioDevicesAddedCallback extends AudioDeviceCallback {
+ @Override
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ for (AudioDeviceInfo deviceInfo : addedDevices) {
+ if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
+ notifyActiveDeviceChanged();
+ if (DBG) {
+ Log.d(TAG, " onAudioDevicesAdded: device type: " + deviceInfo.getType());
+ }
+ mAudioManager.unregisterAudioDeviceCallback(this);
+ }
+ }
+ }
+ }
+
private HearingAidStateMachine getOrCreateStateMachine(BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
@@ -706,6 +767,8 @@ public class HearingAidService extends ProfileService {
Log.d(TAG, "reportActiveDevice(" + device + ")");
}
+ mActiveDevice = device;
+
BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
BluetoothProfile.HEARING_AID, mAdapterService.obfuscateAddress(device),
mAdapterService.getMetricId(device));
@@ -722,6 +785,15 @@ public class HearingAidService extends ProfileService {
Log.d(TAG, "Hearing Aid audio: " + mPreviousAudioDevice + " -> " + device
+ ". Stop audio: " + stopAudio);
}
+
+ if (device != null) {
+ mAudioManager.registerAudioDeviceCallback(mAudioManagerOnAudioDevicesAddedCallback,
+ mHandler);
+ } else {
+ mAudioManager.registerAudioDeviceCallback(mAudioManagerOnAudioDevicesRemovedCallback,
+ mHandler);
+ }
+
mAudioManager.handleBluetoothActiveDeviceChanged(device, mPreviousAudioDevice,
BluetoothProfileConnectionInfo.createHearingAidInfo(!stopAudio));
mPreviousAudioDevice = device;
@@ -767,6 +839,8 @@ public class HearingAidService extends ProfileService {
return;
}
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnecting device because it was unbonded.");
+ disconnect(device);
return;
}
removeStateMachine(device);
@@ -818,7 +892,6 @@ public class HearingAidService extends ProfileService {
BluetoothMetricsProto.ProfileId.HEARING_AID);
}
if (!mHiSyncIdConnectedMap.getOrDefault(myHiSyncId, false)) {
- setActiveDevice(device);
mHiSyncIdConnectedMap.put(myHiSyncId, true);
}
}
@@ -858,12 +931,17 @@ public class HearingAidService extends ProfileService {
@VisibleForTesting
static class BluetoothHearingAidBinder extends IBluetoothHearingAid.Stub
implements IProfileServiceBinder {
+ @VisibleForTesting
+ boolean mIsTesting = false;
private HearingAidService mService;
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private HearingAidService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
- || !Utils.checkServiceAvailable(mService, TAG)
+ if (mIsTesting) {
+ return mService;
+ }
+ if (!Utils.checkServiceAvailable(mService, TAG)
+ || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
}
diff --git a/android/app/src/com/android/bluetooth/hfp/AtPhonebook.java b/android/app/src/com/android/bluetooth/hfp/AtPhonebook.java
index 12f1c4c40ebe672ba42b8ad2219288a0c1d4eb06..8f273aa5389912fb28856b412adae834e9c40b57 100644
--- a/android/app/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/android/app/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -32,10 +32,12 @@ import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.util.DevicePolicyUtils;
import com.android.bluetooth.util.GsmAlphabet;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
@@ -70,7 +72,8 @@ public class AtPhonebook {
private static final String INCOMING_CALL_WHERE = Calls.TYPE + "=" + Calls.INCOMING_TYPE;
private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
- private class PhonebookResult {
+ @VisibleForTesting
+ class PhonebookResult {
public Cursor cursor; // result set of last query
public int numberColumn;
public int numberPresentationColumn;
@@ -81,16 +84,20 @@ public class AtPhonebook {
private Context mContext;
private ContentResolver mContentResolver;
private HeadsetNativeInterface mNativeInterface;
- private String mCurrentPhonebook;
- private String mCharacterSet = "UTF-8";
+ @VisibleForTesting
+ String mCurrentPhonebook;
+ @VisibleForTesting
+ String mCharacterSet = "UTF-8";
- private int mCpbrIndex1, mCpbrIndex2;
+ @VisibleForTesting
+ int mCpbrIndex1, mCpbrIndex2;
private boolean mCheckingAccessPermission;
// package and class name to which we send intent to check phone book access permission
private final String mPairingPackage;
- private final HashMap mPhonebooks =
+ @VisibleForTesting
+ final HashMap mPhonebooks =
new HashMap(4);
static final int TYPE_UNKNOWN = -1;
@@ -387,7 +394,8 @@ public class AtPhonebook {
* If force then re-query that phonebook
* Returns null if the cursor is not ready
*/
- private synchronized PhonebookResult getPhonebookResult(String pb, boolean force) {
+ @VisibleForTesting
+ synchronized PhonebookResult getPhonebookResult(String pb, boolean force) {
if (pb == null) {
return null;
}
@@ -431,8 +439,8 @@ public class AtPhonebook {
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, where);
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, Calls.DEFAULT_SORT_ORDER);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, MAX_PHONEBOOK_SIZE);
- pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION,
- queryArgs, null);
+ pbr.cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
+ Calls.CONTENT_URI, CALLS_PROJECTION, queryArgs, null);
if (pbr.cursor == null) {
return false;
@@ -447,8 +455,8 @@ public class AtPhonebook {
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, where);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, MAX_PHONEBOOK_SIZE);
final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
- pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION,
- queryArgs, null);
+ pbr.cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
+ phoneContentUri, PHONES_PROJECTION, queryArgs, null);
if (pbr.cursor == null) {
return false;
@@ -469,7 +477,8 @@ public class AtPhonebook {
mCheckingAccessPermission = false;
}
- private synchronized int getMaxPhoneBookSize(int currSize) {
+ @VisibleForTesting
+ synchronized int getMaxPhoneBookSize(int currSize) {
// some car kits ignore the current size and request max phone book
// size entries. Thus, it takes a long time to transfer all the
// entries. Use a heuristic to calculate the max phone book size
@@ -543,7 +552,7 @@ public class AtPhonebook {
// try caller id lookup
// TODO: This code is horribly inefficient. I saw it
// take 7 seconds to process 100 missed calls.
- Cursor c = mContentResolver.query(
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number),
new String[]{
PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE
@@ -632,7 +641,8 @@ public class AtPhonebook {
* @return {@link BluetoothDevice#ACCESS_UNKNOWN}, {@link BluetoothDevice#ACCESS_ALLOWED} or
* {@link BluetoothDevice#ACCESS_REJECTED}.
*/
- private int checkAccessPermission(BluetoothDevice remoteDevice) {
+ @VisibleForTesting
+ int checkAccessPermission(BluetoothDevice remoteDevice) {
log("checkAccessPermission");
int permission = remoteDevice.getPhonebookAccessPermission();
@@ -653,7 +663,8 @@ public class AtPhonebook {
return permission;
}
- private static String getPhoneType(int type) {
+ @VisibleForTesting
+ static String getPhoneType(int type) {
switch (type) {
case Phone.TYPE_HOME:
return "H";
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index be6327ca0747682eeae03219d85d94290950ed97..59d3ecabe0664310e468cea0736b826313422f52 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -69,7 +69,7 @@ public class HeadsetNativeInterface {
} else {
// Service must call cleanup() when quiting and native stack shouldn't send any event
// after cleanup() -> cleanupNative() is called.
- Log.wtf(TAG, "FATAL: Stack sent event while service is not available: " + event);
+ Log.w(TAG, "Stack sent event while service is not available: " + event);
}
}
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 7bfe9f8bb7b34c713bf66555ea497186bb6df414..440cf815748e74228abd7eb32777e148b77c31fc 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -76,23 +76,25 @@ public class HeadsetPhoneState {
private final Object mPhoneStateListenerLock = new Object();
HeadsetPhoneState(HeadsetService headsetService) {
- Objects.requireNonNull(headsetService, "headsetService is null");
- mHeadsetService = headsetService;
- mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class);
- Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
- // Register for SubscriptionInfo list changes which is guaranteed to invoke
- // onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
- mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
- Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
- // Initialize subscription on the handler thread
- mHandler = new Handler(headsetService.getStateMachinesThreadLooper());
- mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener();
- mSubscriptionManager.addOnSubscriptionsChangedListener(command -> mHandler.post(command),
- mOnSubscriptionsChangedListener);
- mSignalStrengthUpdateRequest = new SignalStrengthUpdateRequest.Builder()
- .setSignalThresholdInfos(Collections.EMPTY_LIST)
- .setSystemThresholdReportingRequestedWhileIdle(true)
- .build();
+ synchronized (mPhoneStateListenerLock) {
+ Objects.requireNonNull(headsetService, "headsetService is null");
+ mHeadsetService = headsetService;
+ mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class);
+ Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
+ // Register for SubscriptionInfo list changes which is guaranteed to invoke
+ // onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
+ mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
+ Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
+ // Initialize subscription on the handler thread
+ mHandler = new Handler(headsetService.getStateMachinesThreadLooper());
+ mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener();
+ mSubscriptionManager.addOnSubscriptionsChangedListener(
+ command -> mHandler.post(command), mOnSubscriptionsChangedListener);
+ mSignalStrengthUpdateRequest = new SignalStrengthUpdateRequest.Builder()
+ .setSignalThresholdInfos(Collections.EMPTY_LIST)
+ .setSystemThresholdReportingRequestedWhileIdle(true)
+ .build();
+ }
}
/**
@@ -173,6 +175,7 @@ public class HeadsetPhoneState {
private void stopListenForPhoneState() {
synchronized (mPhoneStateListenerLock) {
+ mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
if (mPhoneStateListener == null) {
Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
return;
@@ -181,7 +184,6 @@ public class HeadsetPhoneState {
+ getTelephonyEventsToListen());
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mPhoneStateListener = null;
- mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
}
}
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 17a7067259aee9ec3c378daf830595d4cf927235..19bffa0c28628b96e07025e77fa0b4ebda332e13 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -23,6 +23,7 @@ import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -54,7 +55,9 @@ import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.le_audio.LeAudioService;
import com.android.bluetooth.telephony.BluetoothInCallService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -141,6 +144,8 @@ public class HeadsetService extends ProfileService {
private boolean mCreated;
private static HeadsetService sHeadsetService;
+ private final ServiceFactory mFactory = new ServiceFactory();
+
public static boolean isEnabled() {
return BluetoothProperties.isProfileHfpAgEnabled().orElse(false);
}
@@ -179,6 +184,7 @@ public class HeadsetService extends ProfileService {
// Step 3: Initialize system interface
mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
// Step 4: Initialize native interface
+ setHeadsetService(this);
mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
// Add 1 to allow a pending device to be connecting or disconnecting
@@ -197,7 +203,6 @@ public class HeadsetService extends ProfileService {
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mHeadsetReceiver, filter);
// Step 7: Mark service as started
- setHeadsetService(this);
mStarted = true;
BluetoothDevice activeDevice = getActiveDevice();
String deviceAddress = activeDevice != null ?
@@ -223,7 +228,6 @@ public class HeadsetService extends ProfileService {
AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS;
mAdapterService.notifyActivityAttributionInfo(getAttributionSource(), deviceAddress);
mStarted = false;
- setHeadsetService(null);
// Step 6: Tear down broadcast receivers
unregisterReceiver(mHeadsetReceiver);
synchronized (mStateMachines) {
@@ -256,6 +260,7 @@ public class HeadsetService extends ProfileService {
}
// Step 4: Destroy native interface
mNativeInterface.cleanup();
+ setHeadsetService(null);
// Step 3: Destroy system interface
mSystemInterface.stop();
// Step 2: Stop handler thread
@@ -458,7 +463,8 @@ public class HeadsetService extends ProfileService {
/**
* Handlers for incoming service calls
*/
- private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub
+ @VisibleForTesting
+ static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub
implements IProfileServiceBinder {
private volatile HeadsetService mService;
@@ -473,7 +479,10 @@ public class HeadsetService extends ProfileService {
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private HeadsetService getService(AttributionSource source) {
- if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+ if (Utils.isInstrumentationTestMode()) {
+ return mService;
+ }
+ if (!Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkServiceAvailable(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
@@ -1359,6 +1368,16 @@ public class HeadsetService extends ProfileService {
*/
private void removeActiveDevice() {
synchronized (mStateMachines) {
+ // As per b/202602952, if we remove the active device due to a disconnection,
+ // we need to check if another device is connected and set it active instead.
+ // Calling this before any other active related calls has the same effect as
+ // a classic active device switch.
+ BluetoothDevice fallbackDevice = getFallbackDevice();
+ if (fallbackDevice != null && mActiveDevice != null
+ && getConnectionState(mActiveDevice) != BluetoothProfile.STATE_CONNECTED) {
+ setActiveDevice(fallbackDevice);
+ return;
+ }
// Clear the active device
if (mVoiceRecognitionStarted) {
if (!stopVoiceRecognition(mActiveDevice)) {
@@ -1428,6 +1447,16 @@ public class HeadsetService extends ProfileService {
}
broadcastActiveDevice(mActiveDevice);
} else if (shouldPersistAudio()) {
+ /* If HFP is getting active for a phonecall and there is LeAudio device active,
+ * Lets inactive LeAudio device as soon as possible so there is no CISes connected
+ * when SCO is created
+ */
+ LeAudioService leAudioService = mFactory.getLeAudioService();
+ if (leAudioService != null) {
+ Log.i(TAG, "Make sure there is no le audio device active.");
+ leAudioService.setInactiveForHfpHandover(mActiveDevice);
+ }
+
broadcastActiveDevice(mActiveDevice);
int connectStatus = connectAudio(mActiveDevice);
if (connectStatus != BluetoothStatusCodes.SUCCESS) {
@@ -1459,7 +1488,7 @@ public class HeadsetService extends ProfileService {
}
}
- int connectAudio() {
+ public int connectAudio() {
synchronized (mStateMachines) {
BluetoothDevice device = mActiveDevice;
if (device == null) {
@@ -1903,7 +1932,12 @@ public class HeadsetService extends ProfileService {
return true;
}
- boolean isInbandRingingEnabled() {
+ /**
+ * Checks if headset devices are able to get inband ringing.
+ *
+ * @return True if inband ringing is enabled.
+ */
+ public boolean isInbandRingingEnabled() {
boolean isInbandRingingSupported = getResources().getBoolean(
com.android.bluetooth.R.bool.config_bluetooth_hfp_inband_ringing_support);
return isInbandRingingSupported && !SystemProperties.getBoolean(
@@ -2149,6 +2183,38 @@ public class HeadsetService extends ProfileService {
== mStateMachinesThread.getId());
}
+ /**
+ * Retrieves the most recently connected device in the A2DP connected devices list.
+ */
+ public BluetoothDevice getFallbackDevice() {
+ DatabaseManager dbManager = mAdapterService.getDatabase();
+ return dbManager != null ? dbManager
+ .getMostRecentlyConnectedDevicesInList(getFallbackCandidates(dbManager))
+ : null;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ List getFallbackCandidates(DatabaseManager dbManager) {
+ List fallbackCandidates = getConnectedDevices();
+ List uninterestedCandidates = new ArrayList<>();
+ for (BluetoothDevice device : fallbackCandidates) {
+ byte[] deviceType = dbManager.getCustomMeta(device,
+ BluetoothDevice.METADATA_DEVICE_TYPE);
+ BluetoothClass deviceClass = device.getBluetoothClass();
+ if ((deviceClass != null
+ && deviceClass.getMajorDeviceClass()
+ == BluetoothClass.Device.WEARABLE_WRIST_WATCH)
+ || (deviceType != null
+ && BluetoothDevice.DEVICE_TYPE_WATCH.equals(new String(deviceType)))) {
+ uninterestedCandidates.add(device);
+ }
+ }
+ for (BluetoothDevice device : uninterestedCandidates) {
+ fallbackCandidates.remove(device);
+ }
+ return fallbackCandidates;
+ }
+
@Override
public void dump(StringBuilder sb) {
boolean isScoOn = mSystemInterface.getAudioManager().isBluetoothScoOn();
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index e431c43bde04082a69d5fcfc2766c1f24f40a43c..b237e7d6cc647b77df87ee2bb6550c9af3188682 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -136,8 +136,10 @@ public class HeadsetStateMachine extends StateMachine {
private final HeadsetSystemInterface mSystemInterface;
// Runtime states
- private int mSpeakerVolume;
- private int mMicVolume;
+ @VisibleForTesting
+ int mSpeakerVolume;
+ @VisibleForTesting
+ int mMicVolume;
private boolean mDeviceSilenced;
private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
// The timestamp when the device entered connecting/connected state
@@ -146,7 +148,8 @@ public class HeadsetStateMachine extends StateMachine {
private boolean mHasNrecEnabled = false;
private boolean mHasWbsEnabled = false;
// AT Phone book keeps a group of states used by AT+CPBR commands
- private final AtPhonebook mPhonebook;
+ @VisibleForTesting
+ final AtPhonebook mPhonebook;
// HSP specific
private boolean mNeedDialingOutReply;
// Audio disconnect timeout retry count
@@ -1516,7 +1519,8 @@ public class HeadsetStateMachine extends StateMachine {
/*
* Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
*/
- private void broadcastVendorSpecificEventIntent(String command, int companyId, int commandType,
+ @VisibleForTesting
+ void broadcastVendorSpecificEventIntent(String command, int companyId, int commandType,
Object[] arguments, BluetoothDevice device) {
log("broadcastVendorSpecificEventIntent(" + command + ")");
Intent intent = new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
@@ -1540,7 +1544,8 @@ public class HeadsetStateMachine extends StateMachine {
am.setBluetoothHeadsetProperties(getCurrentDeviceName(), mHasNrecEnabled, mHasWbsEnabled);
}
- private String parseUnknownAt(String atString) {
+ @VisibleForTesting
+ String parseUnknownAt(String atString) {
StringBuilder atCommand = new StringBuilder(atString.length());
for (int i = 0; i < atString.length(); i++) {
@@ -1561,7 +1566,8 @@ public class HeadsetStateMachine extends StateMachine {
return atCommand.toString();
}
- private int getAtCommandType(String atCommand) {
+ @VisibleForTesting
+ int getAtCommandType(String atCommand) {
int commandType = AtPhonebook.TYPE_UNKNOWN;
String atString = null;
atCommand = atCommand.trim();
@@ -1642,7 +1648,8 @@ public class HeadsetStateMachine extends StateMachine {
}
}
- private void processVolumeEvent(int volumeType, int volume) {
+ @VisibleForTesting
+ void processVolumeEvent(int volumeType, int volume) {
// Only current active device can change SCO volume
if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
@@ -1691,7 +1698,8 @@ public class HeadsetStateMachine extends StateMachine {
}
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- private void processAtChld(int chld, BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtChld(int chld, BluetoothDevice device) {
if (mSystemInterface.processChld(chld)) {
mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
} else {
@@ -1700,7 +1708,8 @@ public class HeadsetStateMachine extends StateMachine {
}
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- private void processSubscriberNumberRequest(BluetoothDevice device) {
+ @VisibleForTesting
+ void processSubscriberNumberRequest(BluetoothDevice device) {
String number = mSystemInterface.getSubscriberNumber();
if (number != null) {
mNativeInterface.atResponseString(device,
@@ -1735,7 +1744,8 @@ public class HeadsetStateMachine extends StateMachine {
}
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- private void processAtCops(BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtCops(BluetoothDevice device) {
// Get operator name suggested by Telephony
String operatorName = null;
ServiceState serviceState = mSystemInterface.getHeadsetPhoneState().getServiceState();
@@ -1756,7 +1766,8 @@ public class HeadsetStateMachine extends StateMachine {
}
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- private void processAtClcc(BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtClcc(BluetoothDevice device) {
if (mHeadsetService.isVirtualCallStarted()) {
// In virtual call, send our phone number instead of remote phone number
String phoneNumber = mSystemInterface.getSubscriberNumber();
@@ -1777,7 +1788,8 @@ public class HeadsetStateMachine extends StateMachine {
}
}
- private void processAtCscs(String atString, int type, BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtCscs(String atString, int type, BluetoothDevice device) {
log("processAtCscs - atString = " + atString);
if (mPhonebook != null) {
mPhonebook.handleCscsCommand(atString, type, device);
@@ -1787,7 +1799,8 @@ public class HeadsetStateMachine extends StateMachine {
}
}
- private void processAtCpbs(String atString, int type, BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtCpbs(String atString, int type, BluetoothDevice device) {
log("processAtCpbs - atString = " + atString);
if (mPhonebook != null) {
mPhonebook.handleCpbsCommand(atString, type, device);
@@ -1797,7 +1810,8 @@ public class HeadsetStateMachine extends StateMachine {
}
}
- private void processAtCpbr(String atString, int type, BluetoothDevice device) {
+ @VisibleForTesting
+ void processAtCpbr(String atString, int type, BluetoothDevice device) {
log("processAtCpbr - atString = " + atString);
if (mPhonebook != null) {
mPhonebook.handleCpbrCommand(atString, type, device);
@@ -1811,7 +1825,8 @@ public class HeadsetStateMachine extends StateMachine {
* Find a character ch, ignoring quoted sections.
* Return input.length() if not found.
*/
- private static int findChar(char ch, String input, int fromIndex) {
+ @VisibleForTesting
+ static int findChar(char ch, String input, int fromIndex) {
for (int i = fromIndex; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '"') {
@@ -1831,7 +1846,8 @@ public class HeadsetStateMachine extends StateMachine {
* Integer arguments are turned into Integer objects. Otherwise a String
* object is used.
*/
- private static Object[] generateArgs(String input) {
+ @VisibleForTesting
+ static Object[] generateArgs(String input) {
int i = 0;
int j;
ArrayList