From f40e61b951c1bc75445d7166220f7f7f3e02c143 Mon Sep 17 00:00:00 2001 From: Jayden Kim Date: Wed, 29 Jun 2022 00:59:08 +0000 Subject: [PATCH 001/998] Extend scan timeout logic to downgrade regular scan - Apply scan timeout for the filtered regular scan - Bug fix for scan timeout logic - Add unit test cases to verify scan control logic Tag: #feature Bug: 233614676 Test: atest BluetoothInstrumentationTests:ScanManagerTest Ignore-AOSP-First: T-QPR1 change Change-Id: I5a8faaa3f12661750ee580b20a58a07ec11b4efc Merged-In: I5a8faaa3f12661750ee580b20a58a07ec11b4efc --- .../bluetooth/btservice/AdapterService.java | 39 +- .../btservice/BluetoothAdapterProxy.java | 75 +++ .../android/bluetooth/gatt/AppScanStats.java | 23 - .../android/bluetooth/gatt/GattService.java | 14 +- .../android/bluetooth/gatt/ScanManager.java | 112 +++- .../bluetooth/gatt/ScanManagerTest.java | 568 ++++++++++++++++++ 6 files changed, 754 insertions(+), 77 deletions(-) create mode 100644 android/app/src/com/android/bluetooth/btservice/BluetoothAdapterProxy.java create mode 100644 android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index f44b6d466ce..4c432d5c0cb 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -5176,33 +5176,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; } } 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 00000000000..4376097c22d --- /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/gatt/AppScanStats.java b/android/app/src/com/android/bluetooth/gatt/AppScanStats.java index cdd11f3c91d..f0b0366d398 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 diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java index 009315d6346..12d3bf1dd41 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -75,6 +75,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; @@ -262,6 +263,7 @@ public class GattService extends ProfileService { private final HashMap mPermits = new HashMap<>(); private AdapterService mAdapterService; + private BluetoothAdapterProxy mBluetoothAdapterProxy; private AdvertiseManager mAdvertiseManager; private PeriodicScanManager mPeriodicScanManager; private ScanManager mScanManager; @@ -319,12 +321,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.start(); - mScanManager = new ScanManager(this, mAdapterService); + mScanManager = new ScanManager(this, mAdapterService, mBluetoothAdapterProxy); mScanManager.start(); mPeriodicScanManager = new PeriodicScanManager(mAdapterService); @@ -426,6 +429,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); diff --git a/android/app/src/com/android/bluetooth/gatt/ScanManager.java b/android/app/src/com/android/bluetooth/gatt/ScanManager.java index 84033cd9807..5e89875eaaf 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. */ @@ -298,8 +312,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 +380,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 +417,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 +451,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 +512,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 +543,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 +577,7 @@ public class ScanManager { if (upgradeScanModeBeforeStart(client)) { return true; } - if (isScreenOn()) { + if (mScreenOn) { return updateScanModeScreenOn(client); } else { return updateScanModeScreenOff(client); @@ -613,6 +635,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 +659,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 +897,19 @@ 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.getScanFromScannerId(client.scannerId) != null) + && (client.stats.getScanFromScannerId(client.scannerId).isTimeout); + } + private boolean isFirstMatchScanClient(ScanClient client) { return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0; @@ -1046,11 +1077,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 @@ -1527,6 +1570,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 +1653,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/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java new file mode 100644 index 00000000000..49aab47aeb6 --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java @@ -0,0 +1,568 @@ +/* + * 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.gatt; + +import static android.bluetooth.le.ScanSettings.SCAN_MODE_OPPORTUNISTIC; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_SCREEN_OFF; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.util.SparseIntArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.rule.ServiceTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.bluetooth.R; +import com.android.bluetooth.TestUtils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.BluetoothAdapterProxy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test cases for {@link ScanManager}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScanManagerTest { + private static final String TAG = ScanManagerTest.class.getSimpleName(); + private static final int DELAY_ASYNC_MS = 10; + private static final int DELAY_DEFAULT_SCAN_TIMEOUT_MS = 1500000; + private static final int DELAY_SCAN_TIMEOUT_MS = 100; + private static final int DEFAULT_SCAN_REPORT_DELAY_MS = 100; + private static final int DEFAULT_NUM_OFFLOAD_SCAN_FILTER = 16; + private static final int DEFAULT_BYTES_OFFLOAD_SCAN_RESULT_STORAGE = 4096; + + private Context mTargetContext; + private GattService mService; + private ScanManager mScanManager; + private Handler mHandler; + private CountDownLatch mLatch; + + @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); + @Mock private AdapterService mAdapterService; + @Mock private BluetoothAdapterProxy mBluetoothAdapterProxy; + + @Before + public void setUp() throws Exception { + mTargetContext = InstrumentationRegistry.getTargetContext(); + Assume.assumeTrue("Ignore test when GattService is not enabled" + , GattService.isEnabled()); + MockitoAnnotations.initMocks(this); + + TestUtils.setAdapterService(mAdapterService); + doReturn(true).when(mAdapterService).isStartedProfile(anyString()); + when(mAdapterService.getScanTimeoutMillis()). + thenReturn((long)DELAY_DEFAULT_SCAN_TIMEOUT_MS); + when(mAdapterService.getNumOfOffloadedScanFilterSupported()) + .thenReturn(DEFAULT_NUM_OFFLOAD_SCAN_FILTER); + when(mAdapterService.getOffloadedScanResultStorage()) + .thenReturn(DEFAULT_BYTES_OFFLOAD_SCAN_RESULT_STORAGE); + + BluetoothAdapterProxy.setInstanceForTesting(mBluetoothAdapterProxy); + // TODO: Need to handle Native call/callback for hw filter configuration when return true + when(mBluetoothAdapterProxy.isOffloadedScanFilteringSupported()).thenReturn(false); + + TestUtils.startService(mServiceRule, GattService.class); + mService = GattService.getGattService(); + assertThat(mService).isNotNull(); + + mScanManager = mService.getScanManager(); + assertThat(mScanManager).isNotNull(); + + mHandler = mScanManager.getClientHandler(); + assertThat(mHandler).isNotNull(); + + mLatch = new CountDownLatch(1); + assertThat(mLatch).isNotNull(); + } + + @After + public void tearDown() throws Exception { + if (!GattService.isEnabled()) { + return; + } + doReturn(false).when(mAdapterService).isStartedProfile(anyString()); + TestUtils.stopService(mServiceRule, GattService.class); + mService = GattService.getGattService(); + assertThat(mService).isNull(); + TestUtils.clearAdapterService(mAdapterService); + BluetoothAdapterProxy.setInstanceForTesting(null); + } + + private void testSleep(long millis) { + try { + mLatch.await(millis, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Log.e(TAG, "Latch await", e); + } + } + + private void sendMessageWaitForProcessed(Message msg) { + if (mHandler == null) { + Log.e(TAG, "sendMessage: mHandler is null."); + return; + } + mHandler.sendMessage(msg); + // Wait for async work from handler thread + TestUtils.waitForLooperToBeIdle(mHandler.getLooper()); + } + + private ScanClient createScanClient(int id, boolean isFiltered, int scanMode, + boolean isBatch) { + List scanFilterList = createScanFilterList(isFiltered); + ScanSettings scanSettings = createScanSettings(scanMode, isBatch); + + ScanClient client = new ScanClient(id, scanSettings, scanFilterList); + client.stats = new AppScanStats("Test", null, null, mService); + client.stats.recordScanStart(scanSettings, scanFilterList, isFiltered, false, id); + return client; + } + + private ScanClient createScanClient(int id, boolean isFiltered, int scanMode) { + return createScanClient(id, isFiltered, scanMode, false); + } + + private List createScanFilterList(boolean isFiltered) { + List scanFilterList = null; + if (isFiltered) { + scanFilterList = new ArrayList<>(); + scanFilterList.add(new ScanFilter.Builder().setDeviceName("TestName").build()); + } + return scanFilterList; + } + + private ScanSettings createScanSettings(int scanMode, boolean isBatch) { + + ScanSettings scanSettings = null; + if(isBatch) { + scanSettings = new ScanSettings.Builder().setScanMode(scanMode) + .setReportDelay(DEFAULT_SCAN_REPORT_DELAY_MS).build(); + } else { + scanSettings = new ScanSettings.Builder().setScanMode(scanMode).build(); + } + return scanSettings; + } + + private Message createStartStopScanMessage(boolean isStartScan, Object obj) { + Message message = new Message(); + message.what = isStartScan ? ScanManager.MSG_START_BLE_SCAN : ScanManager.MSG_STOP_BLE_SCAN; + message.obj = obj; + return message; + } + + private Message createScreenOnOffMessage(boolean isScreenOn) { + Message message = new Message(); + message.what = isScreenOn ? ScanManager.MSG_SCREEN_ON : ScanManager.MSG_SCREEN_OFF; + message.obj = null; + return message; + } + + private Message createImportanceMessage(boolean isForeground) { + final int importance = isForeground ? ActivityManager.RunningAppProcessInfo + .IMPORTANCE_FOREGROUND_SERVICE : ActivityManager.RunningAppProcessInfo + .IMPORTANCE_FOREGROUND_SERVICE + 1; + final int uid = Binder.getCallingUid(); + Message message = new Message(); + message.what = ScanManager.MSG_IMPORTANCE_CHANGE; + message.obj = new ScanManager.UidImportance(uid, importance); + return message; + } + + @Test + public void testScreenOffStartUnfilteredScan() { + // Set filtered scan flag + final boolean isFiltered = false; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_AMBIENT_DISCOVERY); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testScreenOffStartFilteredScan() { + // Set filtered scan flag + final boolean isFiltered = true; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF_BALANCED); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testScreenOnStartUnfilteredScan() { + // Set filtered scan flag + final boolean isFiltered = false; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_AMBIENT_DISCOVERY); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testScreenOnStartFilteredScan() { + // Set filtered scan flag + final boolean isFiltered = true; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_AMBIENT_DISCOVERY); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testResumeUnfilteredScanAfterScreenOn() { + // Set filtered scan flag + final boolean isFiltered = false; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF_BALANCED); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + } + } + + @Test + public void testResumeFilteredScanAfterScreenOn() { + // Set filtered scan flag + final boolean isFiltered = true; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF_BALANCED); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_LATENCY); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF_BALANCED); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + } + } + + @Test + public void testUnfilteredScanTimeout() { + // Set filtered scan flag + final boolean isFiltered = false; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_OPPORTUNISTIC); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_OPPORTUNISTIC); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_OPPORTUNISTIC); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_OPPORTUNISTIC); + // Set scan timeout through Mock + when(mAdapterService.getScanTimeoutMillis()).thenReturn((long)DELAY_SCAN_TIMEOUT_MS); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + // Wait for scan timeout + testSleep(DELAY_SCAN_TIMEOUT_MS + DELAY_ASYNC_MS); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + assertThat(client.stats.getScanFromScannerId(client.scannerId).isTimeout).isTrue(); + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as backgournd app + sendMessageWaitForProcessed(createImportanceMessage(false)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as foreground app + sendMessageWaitForProcessed(createImportanceMessage(true)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testFilteredScanTimeout() { + // Set filtered scan flag + final boolean isFiltered = true; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_LOW_POWER); + // Set scan timeout through Mock + when(mAdapterService.getScanTimeoutMillis()).thenReturn((long)DELAY_SCAN_TIMEOUT_MS); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + // Wait for scan timeout + testSleep(DELAY_SCAN_TIMEOUT_MS + DELAY_ASYNC_MS); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + assertThat(client.stats.getScanFromScannerId(client.scannerId).isTimeout).isTrue(); + // Turn off screen + sendMessageWaitForProcessed(createScreenOnOffMessage(false)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as backgournd app + sendMessageWaitForProcessed(createImportanceMessage(false)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as foreground app + sendMessageWaitForProcessed(createImportanceMessage(true)); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + } + } + + @Test + public void testSwitchForeBackgroundUnfilteredScan() { + // Set filtered scan flag + final boolean isFiltered = false; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_LOW_POWER); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + // Set as backgournd app + sendMessageWaitForProcessed(createImportanceMessage(false)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as foreground app + sendMessageWaitForProcessed(createImportanceMessage(true)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + } + } + + @Test + public void testSwitchForeBackgroundFilteredScan() { + // Set filtered scan flag + final boolean isFiltered = true; + // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} + SparseIntArray scanModeMap = new SparseIntArray(); + scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_LOW_POWER); + scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_LOW_POWER); + + for (int i = 0; i < scanModeMap.size(); i++) { + int ScanMode = scanModeMap.keyAt(i); + int expectedScanMode = scanModeMap.get(ScanMode); + Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + + " expectedScanMode: " + String.valueOf(expectedScanMode)); + + // Turn on screen + sendMessageWaitForProcessed(createScreenOnOffMessage(true)); + // Create scan client + ScanClient client = createScanClient(i, isFiltered, ScanMode); + // Start scan + sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + // Set as backgournd app + sendMessageWaitForProcessed(createImportanceMessage(false)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(expectedScanMode); + // Set as foreground app + sendMessageWaitForProcessed(createImportanceMessage(true)); + assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); + assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); + assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); + } + } +} -- GitLab From 040c9bafc992557d46d52cc01a9a59f9632c9ef5 Mon Sep 17 00:00:00 2001 From: William Escande Date: Sat, 23 Jul 2022 12:28:28 -0700 Subject: [PATCH 002/998] Fix method not enforcing correct permissions This missing enforcement were spotted by the erroprone build Bug: 236759221 Fix: 240301753 Test: m RUN_ERROR_PRONE=true Bluetooth Ignore-AOSP-First: Cherry-picked from aosp Merged-In: I5285f0754775c56a73ad1b6eef4bdb7ec3d59757 Change-Id: I5285f0754775c56a73ad1b6eef4bdb7ec3d59757 --- .../bluetooth/btservice/AdapterService.java | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index f44b6d466ce..b627e07afcc 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -3679,9 +3679,17 @@ public class AdapterService extends Service { receiver.propagateException(e); } } + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) private boolean allowLowLatencyAudio(boolean allowed, BluetoothDevice device) { AdapterService service = getService(); - if (service == null) { + if (service == null + || !Utils.checkCallerIsSystemOrActiveUser(TAG) + || !Utils.checkConnectPermissionForDataDelivery( + service, Utils.getCallingAttributionSource(service), + "AdapterService allowLowLatencyAudio")) { return false; } enforceBluetoothPrivilegedPermission(service); @@ -3697,12 +3705,24 @@ public class AdapterService extends Service { receiver.propagateException(e); } } - public int startRfcommListener( + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + private int startRfcommListener( String name, ParcelUuid uuid, PendingIntent pendingIntent, AttributionSource attributionSource) { - return mService.startRfcommListener(name, uuid, pendingIntent, attributionSource); + AdapterService service = getService(); + if (service == null + || !Utils.checkCallerIsSystemOrActiveUser(TAG) + || !Utils.checkConnectPermissionForDataDelivery( + service, attributionSource, "AdapterService startRfcommListener")) { + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; + } + enforceBluetoothPrivilegedPermission(service); + return service.startRfcommListener(name, uuid, pendingIntent, attributionSource); } @Override @@ -3714,8 +3734,20 @@ public class AdapterService extends Service { receiver.propagateException(e); } } - public int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) { - return mService.stopRfcommListener(uuid, attributionSource); + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + private int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) { + AdapterService service = getService(); + if (service == null + || !Utils.checkCallerIsSystemOrActiveUser(TAG) + || !Utils.checkConnectPermissionForDataDelivery( + service, attributionSource, "AdapterService stopRfcommListener")) { + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; + } + enforceBluetoothPrivilegedPermission(service); + return service.stopRfcommListener(uuid, attributionSource); } @Override @@ -3727,9 +3759,22 @@ public class AdapterService extends Service { receiver.propagateException(e); } } - public IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord( + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + private IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord( ParcelUuid uuid, AttributionSource attributionSource) { - return mService.retrievePendingSocketForServiceRecord(uuid, attributionSource); + AdapterService service = getService(); + if (service == null + || !Utils.checkCallerIsSystemOrActiveUser(TAG) + || !Utils.checkConnectPermissionForDataDelivery( + service, attributionSource, + "AdapterService retrievePendingSocketForServiceRecord")) { + return null; + } + enforceBluetoothPrivilegedPermission(service); + return service.retrievePendingSocketForServiceRecord(uuid, attributionSource); } @Override -- GitLab From 9f083ec910ec38ba7ba04443b126f66ef33972b4 Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Mon, 1 Aug 2022 15:15:11 +0800 Subject: [PATCH 003/998] Add length check when copy AVDTP packet Bug: 232023771 Test: make Tag: #security Ignore-AOSP-First: Security Change-Id: I68dd78c747eeafee5190dc56d7c71e9eeed08a5b --- system/stack/avdt/avdt_msg.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/stack/avdt/avdt_msg.cc b/system/stack/avdt/avdt_msg.cc index a3e71c8f574..7a3ed280cca 100644 --- a/system/stack/avdt/avdt_msg.cc +++ b/system/stack/avdt/avdt_msg.cc @@ -1252,6 +1252,10 @@ BT_HDR* avdt_msg_asmbl(AvdtpCcb* p_ccb, BT_HDR* p_buf) { * would have allocated smaller buffer. */ p_ccb->p_rx_msg = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE); + if (sizeof(BT_HDR) + p_buf->offset + p_buf->len > BT_DEFAULT_BUFFER_SIZE) { + android_errorWriteLog(0x534e4554, "232023771"); + return NULL; + } memcpy(p_ccb->p_rx_msg, p_buf, sizeof(BT_HDR) + p_buf->offset + p_buf->len); /* Free original buffer */ -- GitLab From 490062ebf9ffc27e2b74f2cd68afa17c41279cf9 Mon Sep 17 00:00:00 2001 From: Alice Kuo Date: Fri, 22 Jul 2022 17:44:59 +0800 Subject: [PATCH 004/998] Use PTS flag to set 48_1/2/3/4 audio configuration for broadcast PTS test Bug: 238588093 Test: Pass PTS BAP broadcast source test cases Change-Id: I83b97e47df6af9444f6a7ff8de6979e8a1ee882c Merged-In: I83b97e47df6af9444f6a7ff8de6979e8a1ee882c (cherry picked from commit c8ed593b813e693c71e6c2410cd455a118d1e885) --- system/bta/Android.bp | 2 + .../le_audio/broadcaster/broadcaster_types.cc | 73 +++++++++++++++++++ system/conf/bt_stack.conf | 8 ++ system/internal_include/stack_config.h | 1 + system/main/stack_config.cc | 12 +++ .../avrcp_device_fuzz/avrcp_device_fuzz.cc | 2 +- .../profile/avrcp/tests/avrcp_device_test.cc | 2 +- system/stack/test/btm/stack_btm_test.cc | 7 ++ system/stack/test/stack_smp_test.cc | 7 ++ system/test/common/stack_config.cc | 7 ++ 10 files changed, 119 insertions(+), 2 deletions(-) diff --git a/system/bta/Android.bp b/system/bta/Android.bp index 8129074ceb4..254f0983fee 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -716,6 +716,7 @@ cc_test { "le_audio/le_audio_types.cc", "le_audio/mock_iso_manager.cc", "le_audio/mock_codec_manager.cc", + ":TestCommonStackConfig", ], shared_libs: [ "libprotobuf-cpp-lite", @@ -770,6 +771,7 @@ cc_test { "le_audio/mock_iso_manager.cc", "test/common/mock_controller.cc", "le_audio/mock_codec_manager.cc", + ":TestCommonStackConfig", ], shared_libs: [ "libprotobuf-cpp-lite", diff --git a/system/bta/le_audio/broadcaster/broadcaster_types.cc b/system/bta/le_audio/broadcaster/broadcaster_types.cc index 66048a060df..8020553985e 100644 --- a/system/bta/le_audio/broadcaster/broadcaster_types.cc +++ b/system/bta/le_audio/broadcaster/broadcaster_types.cc @@ -23,6 +23,7 @@ #include "bta_le_audio_broadcaster_api.h" #include "btm_ble_api_types.h" #include "embdrv/lc3/include/lc3.h" +#include "internal_include/stack_config.h" #include "osi/include/properties.h" using bluetooth::le_audio::BasicAudioAnnouncementBisConfig; @@ -223,6 +224,54 @@ static const BroadcastCodecWrapper lc3_stereo_24_2 = BroadcastCodecWrapper( // Frame len. 60); +static const BroadcastCodecWrapper lc3_stereo_48_1 = BroadcastCodecWrapper( + kLeAudioCodecIdLc3, + // LeAudioCodecConfiguration + {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo, + .sample_rate = LeAudioCodecConfiguration::kSampleRate48000, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16, + .data_interval_us = LeAudioCodecConfiguration::kInterval7500Us}, + // Bitrate + 80000, + // Frame len. + 75); + +static const BroadcastCodecWrapper lc3_stereo_48_2 = BroadcastCodecWrapper( + kLeAudioCodecIdLc3, + // LeAudioCodecConfiguration + {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo, + .sample_rate = LeAudioCodecConfiguration::kSampleRate48000, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16, + .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us}, + // Bitrate + 80000, + // Frame len. + 100); + +static const BroadcastCodecWrapper lc3_stereo_48_3 = BroadcastCodecWrapper( + kLeAudioCodecIdLc3, + // LeAudioCodecConfiguration + {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo, + .sample_rate = LeAudioCodecConfiguration::kSampleRate48000, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16, + .data_interval_us = LeAudioCodecConfiguration::kInterval7500Us}, + // Bitrate + 96000, + // Frame len. + 90); + +static const BroadcastCodecWrapper lc3_stereo_48_4 = BroadcastCodecWrapper( + kLeAudioCodecIdLc3, + // LeAudioCodecConfiguration + {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo, + .sample_rate = LeAudioCodecConfiguration::kSampleRate48000, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16, + .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us}, + // Bitrate + 96000, + // Frame len. + 120); + const std::map sample_rate_to_sampling_freq_map = { {LeAudioCodecConfiguration::kSampleRate8000, codec_spec_conf::kLeAudioSamplingFreq8000Hz}, @@ -318,8 +367,12 @@ std::ostream& operator<<( static const BroadcastQosConfig qos_config_2_10 = BroadcastQosConfig(2, 10); +static const BroadcastQosConfig qos_config_4_50 = BroadcastQosConfig(4, 50); + static const BroadcastQosConfig qos_config_4_60 = BroadcastQosConfig(4, 60); +static const BroadcastQosConfig qos_config_4_65 = BroadcastQosConfig(4, 65); + std::ostream& operator<<( std::ostream& os, const le_audio::broadcaster::BroadcastQosConfig& config) { os << " BroadcastQosConfig=["; @@ -344,8 +397,28 @@ static const std::pair static const std::pair lc3_stereo_24_2_2 = {lc3_stereo_24_2, qos_config_4_60}; +static const std::pair + lc3_stereo_48_1_2 = {lc3_stereo_48_1, qos_config_4_50}; + +static const std::pair + lc3_stereo_48_2_2 = {lc3_stereo_48_2, qos_config_4_65}; + +static const std::pair + lc3_stereo_48_3_2 = {lc3_stereo_48_3, qos_config_4_50}; + +static const std::pair + lc3_stereo_48_4_2 = {lc3_stereo_48_4, qos_config_4_65}; + std::pair getStreamConfigForContext(uint16_t context) { + const std::string* options = + stack_config_get_interface()->get_pts_broadcast_audio_config_options(); + if (options) { + if (!options->compare("lc3_stereo_48_1_2")) return lc3_stereo_48_1_2; + if (!options->compare("lc3_stereo_48_2_2")) return lc3_stereo_48_2_2; + if (!options->compare("lc3_stereo_48_3_2")) return lc3_stereo_48_3_2; + if (!options->compare("lc3_stereo_48_4_2")) return lc3_stereo_48_4_2; + } // High quality, Low Latency auto contexts_stereo_24_2_1 = static_cast::type>( diff --git a/system/conf/bt_stack.conf b/system/conf/bt_stack.conf index 942dfdcd1e8..8e2cbe3b0d6 100644 --- a/system/conf/bt_stack.conf +++ b/system/conf/bt_stack.conf @@ -84,3 +84,11 @@ TRC_HID_DEV=2 # SMP_NUMERIC_COMPAR_FAIL = 12 #PTS_SmpFailureCase=0 + +# PTS Broadcast audio configuration option +# Option: +# lc3_stereo_48_1_2 +# lc3_stereo_48_2_2 +# lc3_stereo_48_3_2 +# lc3_stereo_48_4_2 +#PTS_BroadcastAudioConfigOption=lc3_stereo_48_1_2 diff --git a/system/internal_include/stack_config.h b/system/internal_include/stack_config.h index 53dd186a705..31d40670f25 100644 --- a/system/internal_include/stack_config.h +++ b/system/internal_include/stack_config.h @@ -38,6 +38,7 @@ typedef struct { bool (*get_pts_connect_eatt_before_encryption)(void); bool (*get_pts_unencrypt_broadcast)(void); bool (*get_pts_eatt_peripheral_collision_support)(void); + const std::string* (*get_pts_broadcast_audio_config_options)(void); config_t* (*get_all)(void); } stack_config_t; diff --git a/system/main/stack_config.cc b/system/main/stack_config.cc index fdfe7d60592..2da90501378 100644 --- a/system/main/stack_config.cc +++ b/system/main/stack_config.cc @@ -40,6 +40,8 @@ const char* PTS_CONNECT_EATT_UNENCRYPTED = "PTS_ConnectEattUnencrypted"; const char* PTS_BROADCAST_UNENCRYPTED = "PTS_BroadcastUnencrypted"; const char* PTS_EATT_PERIPHERAL_COLLISION_SUPPORT = "PTS_EattPeripheralCollionSupport"; +const char* PTS_BROADCAST_AUDIO_CONFIG_OPTION = + "PTS_BroadcastAudioConfigOption"; static std::unique_ptr config; } // namespace @@ -142,6 +144,15 @@ static bool get_pts_eatt_peripheral_collision_support(void) { PTS_EATT_PERIPHERAL_COLLISION_SUPPORT, false); } +static const std::string* get_pts_broadcast_audio_config_options(void) { + if (!config) { + LOG_INFO("Config isn't ready, use default option"); + return NULL; + } + return config_get_string(*config, CONFIG_DEFAULT_SECTION, + PTS_BROADCAST_AUDIO_CONFIG_OPTION, NULL); +} + static config_t* get_all(void) { return config.get(); } const stack_config_t interface = {get_trace_config_enabled, @@ -156,6 +167,7 @@ const stack_config_t interface = {get_trace_config_enabled, get_pts_connect_eatt_before_encryption, get_pts_unencrypt_broadcast, get_pts_eatt_peripheral_collision_support, + get_pts_broadcast_audio_config_options, get_all}; const stack_config_t* stack_config_get_interface(void) { return &interface; } diff --git a/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc b/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc index 441bf5c1d05..8aeff6b27ca 100644 --- a/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc +++ b/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc @@ -60,7 +60,7 @@ const stack_config_t interface = {nullptr, get_pts_avrcp_test, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr}; + nullptr, nullptr}; void Callback(uint8_t, bool, std::unique_ptr<::bluetooth::PacketBuilder>) {} diff --git a/system/profile/avrcp/tests/avrcp_device_test.cc b/system/profile/avrcp/tests/avrcp_device_test.cc index 2bbc39ad4eb..c69a4adc189 100644 --- a/system/profile/avrcp/tests/avrcp_device_test.cc +++ b/system/profile/avrcp/tests/avrcp_device_test.cc @@ -56,7 +56,7 @@ const stack_config_t interface = {nullptr, get_pts_avrcp_test, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr}; + nullptr, nullptr}; // TODO (apanicke): All the tests below are just basic positive unit tests. // Add more tests to increase code coverage. diff --git a/system/stack/test/btm/stack_btm_test.cc b/system/stack/test/btm/stack_btm_test.cc index e278e44869d..d4b78f52b36 100644 --- a/system/stack/test/btm/stack_btm_test.cc +++ b/system/stack/test/btm/stack_btm_test.cc @@ -62,6 +62,8 @@ const hci_t* hci_layer_get_interface() { return nullptr; } void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {} const std::string kSmpOptions("mock smp options"); +const std::string kBroadcastAudioConfigOptions( + "mock broadcast audio config options"); bool get_trace_config_enabled(void) { return false; } bool get_pts_avrcp_test(void) { return false; } @@ -75,6 +77,9 @@ bool get_pts_connect_eatt_unconditionally(void) { return false; } bool get_pts_connect_eatt_before_encryption(void) { return false; } bool get_pts_unencrypt_broadcast(void) { return false; } bool get_pts_eatt_peripheral_collision_support(void) { return false; } +const std::string* get_pts_broadcast_audio_config_options(void) { + return &kBroadcastAudioConfigOptions; +} config_t* get_all(void) { return nullptr; } const packet_fragmenter_t* packet_fragmenter_get_interface() { return nullptr; } @@ -95,6 +100,8 @@ stack_config_t mock_stack_config{ .get_pts_unencrypt_broadcast = get_pts_unencrypt_broadcast, .get_pts_eatt_peripheral_collision_support = get_pts_eatt_peripheral_collision_support, + .get_pts_broadcast_audio_config_options = + get_pts_broadcast_audio_config_options, .get_all = get_all, }; const stack_config_t* stack_config_get_interface(void) { diff --git a/system/stack/test/stack_smp_test.cc b/system/stack/test/stack_smp_test.cc index abf10b9edff..7a12fc90727 100644 --- a/system/stack/test/stack_smp_test.cc +++ b/system/stack/test/stack_smp_test.cc @@ -38,6 +38,8 @@ tBTM_CB btm_cb; std::map mock_function_count_map; const std::string kSmpOptions("mock smp options"); +const std::string kBroadcastAudioConfigOptions( + "mock broadcast audio config options"); bool get_trace_config_enabled(void) { return false; } bool get_pts_avrcp_test(void) { return false; } bool get_pts_secure_only_mode(void) { return false; } @@ -50,6 +52,9 @@ bool get_pts_connect_eatt_unconditionally(void) { return false; } bool get_pts_connect_eatt_before_encryption(void) { return false; } bool get_pts_unencrypt_broadcast(void) { return false; } bool get_pts_eatt_peripheral_collision_support(void) { return false; } +const std::string* get_pts_broadcast_audio_config_options(void) { + return &kBroadcastAudioConfigOptions; +} config_t* get_all(void) { return nullptr; } const packet_fragmenter_t* packet_fragmenter_get_interface() { return nullptr; } @@ -70,6 +75,8 @@ stack_config_t mock_stack_config{ .get_pts_unencrypt_broadcast = get_pts_unencrypt_broadcast, .get_pts_eatt_peripheral_collision_support = get_pts_eatt_peripheral_collision_support, + .get_pts_broadcast_audio_config_options = + get_pts_broadcast_audio_config_options, .get_all = get_all, }; const stack_config_t* stack_config_get_interface(void) { diff --git a/system/test/common/stack_config.cc b/system/test/common/stack_config.cc index becbabd4961..ffc1ccbc15c 100644 --- a/system/test/common/stack_config.cc +++ b/system/test/common/stack_config.cc @@ -23,6 +23,8 @@ #include const std::string kSmpOptions("mock smp options"); +const std::string kBroadcastAudioConfigOptions( + "mock broadcast audio config options"); bool get_trace_config_enabled(void) { return false; } bool get_pts_avrcp_test(void) { return false; } bool get_pts_secure_only_mode(void) { return false; } @@ -35,6 +37,9 @@ bool get_pts_connect_eatt_unconditionally(void) { return false; } bool get_pts_connect_eatt_before_encryption(void) { return false; } bool get_pts_unencrypt_broadcast(void) { return false; } bool get_pts_eatt_peripheral_collision_support(void) { return false; } +const std::string* get_pts_broadcast_audio_config_options(void) { + return &kBroadcastAudioConfigOptions; +} struct config_t; config_t* get_all(void) { return nullptr; } struct packet_fragmenter_t; @@ -57,6 +62,8 @@ stack_config_t mock_stack_config{ .get_pts_unencrypt_broadcast = get_pts_unencrypt_broadcast, .get_pts_eatt_peripheral_collision_support = get_pts_eatt_peripheral_collision_support, + .get_pts_broadcast_audio_config_options = + get_pts_broadcast_audio_config_options, .get_all = get_all, }; -- GitLab From 5ea759a46638359fa45de000540dcafd66e81c2b Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Tue, 2 Aug 2022 10:28:34 +0800 Subject: [PATCH 005/998] Check if need codec switching before start streaming Bug: 226441860 Test: run sink device using bds-dev Tag: #feature Ignore-AOSP-First: TM QPR1 Feature Change-Id: Ic7bccdc29abcb3861eda4620904e9372e3814d3c --- system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc index faa735a2e00..72cfd35ce54 100644 --- a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc +++ b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc @@ -84,6 +84,8 @@ BluetoothAudioCtrlAck A2dpTransport::StartRequest(bool is_low_latency) { return a2dp_ack_to_bt_audio_ctrl_ack(A2DP_CTRL_ACK_SUCCESS); } if (btif_av_stream_ready()) { + // check if codec needs to be switched prior to stream start + invoke_switch_codec_cb(is_low_latency); /* * Post start event and wait for audio path to open. * If we are the source, the ACK will be sent after the start @@ -96,7 +98,6 @@ BluetoothAudioCtrlAck A2dpTransport::StartRequest(bool is_low_latency) { return a2dp_ack_to_bt_audio_ctrl_ack(A2DP_CTRL_ACK_PENDING); } a2dp_pending_cmd_ = A2DP_CTRL_CMD_NONE; - invoke_switch_codec_cb(is_low_latency); return a2dp_ack_to_bt_audio_ctrl_ack(A2DP_CTRL_ACK_SUCCESS); } LOG(ERROR) << __func__ << ": AV stream is not ready to start"; -- GitLab From ed9a843cf147bbfa1a80f2507769014958940eb4 Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Mon, 1 Aug 2022 15:15:11 +0800 Subject: [PATCH 006/998] Add length check when copy AVDTP packet Bug: 232023771 Test: make Tag: #security Ignore-AOSP-First: Security Change-Id: I68dd78c747eeafee5190dc56d7c71e9eeed08a5b Merged-In: I68dd78c747eeafee5190dc56d7c71e9eeed08a5b --- system/stack/avdt/avdt_msg.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/stack/avdt/avdt_msg.cc b/system/stack/avdt/avdt_msg.cc index a3e71c8f574..7a3ed280cca 100644 --- a/system/stack/avdt/avdt_msg.cc +++ b/system/stack/avdt/avdt_msg.cc @@ -1252,6 +1252,10 @@ BT_HDR* avdt_msg_asmbl(AvdtpCcb* p_ccb, BT_HDR* p_buf) { * would have allocated smaller buffer. */ p_ccb->p_rx_msg = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE); + if (sizeof(BT_HDR) + p_buf->offset + p_buf->len > BT_DEFAULT_BUFFER_SIZE) { + android_errorWriteLog(0x534e4554, "232023771"); + return NULL; + } memcpy(p_ccb->p_rx_msg, p_buf, sizeof(BT_HDR) + p_buf->offset + p_buf->len); /* Free original buffer */ -- GitLab From 62e29ee6f52d995cdace2d1ef8880c11831135fc Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Fri, 1 Apr 2022 11:22:34 +0800 Subject: [PATCH 007/998] Fix potential interger overflow when parsing vendor response Add check for str_len to prevent potential OOB read in vendor response. Bug: 205570663 Tag: #security Test: net_test_stack:StackAvrcpTest Ignore-AOSP-First: Security Change-Id: Iea2c3e17c2c8cc56468c4456822e1c4c5c15f5bc Merged-Id: Iea2c3e17c2c8cc56468c4456822e1c4c5c15f5bc --- system/stack/avrc/avrc_pars_ct.cc | 27 ++++++++++++--- system/stack/test/stack_avrcp_test.cc | 50 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/system/stack/avrc/avrc_pars_ct.cc b/system/stack/avrc/avrc_pars_ct.cc index 12aee4ce69e..a5710428f40 100644 --- a/system/stack/avrc/avrc_pars_ct.cc +++ b/system/stack/avrc/avrc_pars_ct.cc @@ -237,7 +237,7 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, } BE_STREAM_TO_UINT8(pdu, p); uint16_t pkt_len; - int min_len = 0; + uint16_t min_len = 0; /* read the entire packet len */ BE_STREAM_TO_UINT16(pkt_len, p); @@ -380,8 +380,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, /* Parse the name now */ BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p); BE_STREAM_TO_UINT16(attr_entry->name.str_len, p); + if (static_cast(min_len + attr_entry->name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (pkt_len - min_len < attr_entry->name.str_len) + goto browse_length_error; min_len += attr_entry->name.str_len; - if (pkt_len < min_len) goto browse_length_error; attr_entry->name.p_str = (uint8_t*)osi_malloc( attr_entry->name.str_len * sizeof(uint8_t)); BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str, @@ -444,8 +450,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, BE_STREAM_TO_UINT32(attr_entry->attr_id, p); BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p); BE_STREAM_TO_UINT16(attr_entry->name.str_len, p); + if (static_cast(min_len + attr_entry->name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (pkt_len - min_len < attr_entry->name.str_len) + goto browse_length_error; min_len += attr_entry->name.str_len; - if (pkt_len < min_len) goto browse_length_error; attr_entry->name.p_str = (uint8_t*)osi_malloc(attr_entry->name.str_len * sizeof(uint8_t)); BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str, attr_entry->name.str_len); @@ -815,8 +827,12 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg, BE_STREAM_TO_UINT32(p_attrs[i].attr_id, p); BE_STREAM_TO_UINT16(p_attrs[i].name.charset_id, p); BE_STREAM_TO_UINT16(p_attrs[i].name.str_len, p); - min_len += p_attrs[i].name.str_len; - if (len < min_len) { + if (static_cast(min_len + p_attrs[i].name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (len - min_len < p_attrs[i].name.str_len) { for (int j = 0; j < i; j++) { osi_free(p_attrs[j].name.p_str); } @@ -824,6 +840,7 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg, p_result->get_attrs.num_attrs = 0; goto length_error; } + min_len += p_attrs[i].name.str_len; if (p_attrs[i].name.str_len > 0) { p_attrs[i].name.p_str = (uint8_t*)osi_calloc(p_attrs[i].name.str_len); diff --git a/system/stack/test/stack_avrcp_test.cc b/system/stack/test/stack_avrcp_test.cc index 72ec45f290d..e731e98b765 100644 --- a/system/stack/test/stack_avrcp_test.cc +++ b/system/stack/test/stack_avrcp_test.cc @@ -27,6 +27,56 @@ class StackAvrcpTest : public ::testing::Test { virtual ~StackAvrcpTest() = default; }; +TEST_F(StackAvrcpTest, test_avrcp_ctrl_parse_vendor_rsp) { + uint8_t scratch_buf[512]{}; + uint16_t scratch_buf_len = 512; + tAVRC_MSG msg{}; + tAVRC_RESPONSE result{}; + uint8_t vendor_rsp_buf[512]{}; + + msg.hdr.opcode = AVRC_OP_VENDOR; + msg.hdr.ctype = AVRC_CMD_STATUS; + + memset(vendor_rsp_buf, 0, sizeof(vendor_rsp_buf)); + vendor_rsp_buf[0] = AVRC_PDU_GET_ELEMENT_ATTR; + uint8_t* p = &vendor_rsp_buf[2]; + UINT16_TO_BE_STREAM(p, 0x0009); // parameter length + UINT8_TO_STREAM(p, 0x01); // number of attributes + UINT32_TO_STREAM(p, 0x00000000); // attribute ID + UINT16_TO_STREAM(p, 0x0000); // character set ID + UINT16_TO_STREAM(p, 0xffff); // attribute value length + msg.vendor.p_vendor_data = vendor_rsp_buf; + msg.vendor.vendor_len = 13; + EXPECT_EQ( + AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len), + AVRC_STS_INTERNAL_ERR); +} + +TEST_F(StackAvrcpTest, test_avrcp_parse_browse_rsp) { + uint8_t scratch_buf[512]{}; + uint16_t scratch_buf_len = 512; + tAVRC_MSG msg{}; + tAVRC_RESPONSE result{}; + uint8_t browse_rsp_buf[512]{}; + + msg.hdr.opcode = AVRC_OP_BROWSE; + + memset(browse_rsp_buf, 0, sizeof(browse_rsp_buf)); + browse_rsp_buf[0] = AVRC_PDU_GET_ITEM_ATTRIBUTES; + uint8_t* p = &browse_rsp_buf[1]; + UINT16_TO_BE_STREAM(p, 0x000a); // parameter length; + UINT8_TO_STREAM(p, 0x04); // status + UINT8_TO_STREAM(p, 0x01); // number of attribute + UINT32_TO_STREAM(p, 0x00000000); // attribute ID + UINT16_TO_STREAM(p, 0x0000); // character set ID + UINT16_TO_STREAM(p, 0xffff); // attribute value length + msg.browse.p_browse_data = browse_rsp_buf; + msg.browse.browse_len = 13; + EXPECT_EQ( + AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len), + AVRC_STS_BAD_CMD); +} + TEST_F(StackAvrcpTest, test_avrcp_parse_browse_cmd) { uint8_t scratch_buf[512]{}; tAVRC_MSG msg{}; -- GitLab From bc58c5a9545d43c2c0c63bfd02c018ad56ffe9ff Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Fri, 1 Apr 2022 11:22:34 +0800 Subject: [PATCH 008/998] Fix potential interger overflow when parsing vendor response Add check for str_len to prevent potential OOB read in vendor response. Bug: 205570663 Tag: #security Test: net_test_stack:StackAvrcpTest Ignore-AOSP-First: Security Change-Id: Iea2c3e17c2c8cc56468c4456822e1c4c5c15f5bc Merged-Id: Iea2c3e17c2c8cc56468c4456822e1c4c5c15f5bc --- system/stack/avrc/avrc_pars_ct.cc | 27 ++++++++++++--- system/stack/test/stack_avrcp_test.cc | 50 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/system/stack/avrc/avrc_pars_ct.cc b/system/stack/avrc/avrc_pars_ct.cc index 12aee4ce69e..a5710428f40 100644 --- a/system/stack/avrc/avrc_pars_ct.cc +++ b/system/stack/avrc/avrc_pars_ct.cc @@ -237,7 +237,7 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, } BE_STREAM_TO_UINT8(pdu, p); uint16_t pkt_len; - int min_len = 0; + uint16_t min_len = 0; /* read the entire packet len */ BE_STREAM_TO_UINT16(pkt_len, p); @@ -380,8 +380,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, /* Parse the name now */ BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p); BE_STREAM_TO_UINT16(attr_entry->name.str_len, p); + if (static_cast(min_len + attr_entry->name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (pkt_len - min_len < attr_entry->name.str_len) + goto browse_length_error; min_len += attr_entry->name.str_len; - if (pkt_len < min_len) goto browse_length_error; attr_entry->name.p_str = (uint8_t*)osi_malloc( attr_entry->name.str_len * sizeof(uint8_t)); BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str, @@ -444,8 +450,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg, BE_STREAM_TO_UINT32(attr_entry->attr_id, p); BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p); BE_STREAM_TO_UINT16(attr_entry->name.str_len, p); + if (static_cast(min_len + attr_entry->name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (pkt_len - min_len < attr_entry->name.str_len) + goto browse_length_error; min_len += attr_entry->name.str_len; - if (pkt_len < min_len) goto browse_length_error; attr_entry->name.p_str = (uint8_t*)osi_malloc(attr_entry->name.str_len * sizeof(uint8_t)); BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str, attr_entry->name.str_len); @@ -815,8 +827,12 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg, BE_STREAM_TO_UINT32(p_attrs[i].attr_id, p); BE_STREAM_TO_UINT16(p_attrs[i].name.charset_id, p); BE_STREAM_TO_UINT16(p_attrs[i].name.str_len, p); - min_len += p_attrs[i].name.str_len; - if (len < min_len) { + if (static_cast(min_len + p_attrs[i].name.str_len) < + min_len) { + // Check for overflow + android_errorWriteLog(0x534e4554, "205570663"); + } + if (len - min_len < p_attrs[i].name.str_len) { for (int j = 0; j < i; j++) { osi_free(p_attrs[j].name.p_str); } @@ -824,6 +840,7 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg, p_result->get_attrs.num_attrs = 0; goto length_error; } + min_len += p_attrs[i].name.str_len; if (p_attrs[i].name.str_len > 0) { p_attrs[i].name.p_str = (uint8_t*)osi_calloc(p_attrs[i].name.str_len); diff --git a/system/stack/test/stack_avrcp_test.cc b/system/stack/test/stack_avrcp_test.cc index 72ec45f290d..e731e98b765 100644 --- a/system/stack/test/stack_avrcp_test.cc +++ b/system/stack/test/stack_avrcp_test.cc @@ -27,6 +27,56 @@ class StackAvrcpTest : public ::testing::Test { virtual ~StackAvrcpTest() = default; }; +TEST_F(StackAvrcpTest, test_avrcp_ctrl_parse_vendor_rsp) { + uint8_t scratch_buf[512]{}; + uint16_t scratch_buf_len = 512; + tAVRC_MSG msg{}; + tAVRC_RESPONSE result{}; + uint8_t vendor_rsp_buf[512]{}; + + msg.hdr.opcode = AVRC_OP_VENDOR; + msg.hdr.ctype = AVRC_CMD_STATUS; + + memset(vendor_rsp_buf, 0, sizeof(vendor_rsp_buf)); + vendor_rsp_buf[0] = AVRC_PDU_GET_ELEMENT_ATTR; + uint8_t* p = &vendor_rsp_buf[2]; + UINT16_TO_BE_STREAM(p, 0x0009); // parameter length + UINT8_TO_STREAM(p, 0x01); // number of attributes + UINT32_TO_STREAM(p, 0x00000000); // attribute ID + UINT16_TO_STREAM(p, 0x0000); // character set ID + UINT16_TO_STREAM(p, 0xffff); // attribute value length + msg.vendor.p_vendor_data = vendor_rsp_buf; + msg.vendor.vendor_len = 13; + EXPECT_EQ( + AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len), + AVRC_STS_INTERNAL_ERR); +} + +TEST_F(StackAvrcpTest, test_avrcp_parse_browse_rsp) { + uint8_t scratch_buf[512]{}; + uint16_t scratch_buf_len = 512; + tAVRC_MSG msg{}; + tAVRC_RESPONSE result{}; + uint8_t browse_rsp_buf[512]{}; + + msg.hdr.opcode = AVRC_OP_BROWSE; + + memset(browse_rsp_buf, 0, sizeof(browse_rsp_buf)); + browse_rsp_buf[0] = AVRC_PDU_GET_ITEM_ATTRIBUTES; + uint8_t* p = &browse_rsp_buf[1]; + UINT16_TO_BE_STREAM(p, 0x000a); // parameter length; + UINT8_TO_STREAM(p, 0x04); // status + UINT8_TO_STREAM(p, 0x01); // number of attribute + UINT32_TO_STREAM(p, 0x00000000); // attribute ID + UINT16_TO_STREAM(p, 0x0000); // character set ID + UINT16_TO_STREAM(p, 0xffff); // attribute value length + msg.browse.p_browse_data = browse_rsp_buf; + msg.browse.browse_len = 13; + EXPECT_EQ( + AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len), + AVRC_STS_BAD_CMD); +} + TEST_F(StackAvrcpTest, test_avrcp_parse_browse_cmd) { uint8_t scratch_buf[512]{}; tAVRC_MSG msg{}; -- GitLab From 419cdecda104f77f1baf080b44d1f4dc1feb66f1 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Mon, 1 Aug 2022 21:31:45 +0000 Subject: [PATCH 009/998] BluetoothPhoneState: call clearSignalStrengthUpdateRequest() at the beginning of stopListenForPhoneState() to make sure it's always called. Resolving issues introduced in aosp/1901910. Per telecom's comment clearSignalStrengthUpdateRequest() must be called before setSignalStrengthUpdateRequest() clearSignalStrengthUpdateRequest() is called in stopListenForPhoneState() while setSignalStrengthUpdateRequest() is called in startListenForPhoneState(). In BluetoothPhoneState we make sure stopListenForPhoneState() is always called before startListenForPhoneState(). However, we may return early for stopListenForPhoneState() and clearSignalStrengthUpdateRequest() won't called. Bug: 240179714 Test: atest BluetoothInstrumentationTests Change-Id: I7aefcb9ab74d89d00aca67d807799dca7a12eb3c Merged-In: I7aefcb9ab74d89d00aca67d807799dca7a12eb3c (cherry picked from 10ef83fe94c62715188094c5e5174a7ef44e0ad9) --- .../com/android/bluetooth/hfp/HeadsetPhoneState.java | 2 +- .../android/bluetooth/hfp/HeadsetPhoneStateTest.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java index 7bfe9f8bb7b..09af6886ba3 100644 --- a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java +++ b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java @@ -173,6 +173,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 +182,6 @@ public class HeadsetPhoneState { + getTelephonyEventsToListen()); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); mPhoneStateListener = null; - mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java index ed40cfe3fc0..b83c7e3b40e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java @@ -126,6 +126,8 @@ public class HeadsetPhoneStateTest { BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1); mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + verify(mTelephonyManager).clearSignalStrengthUpdateRequest( + any(SignalStrengthUpdateRequest.class)); verifyNoMoreInteractions(mTelephonyManager); } @@ -162,7 +164,7 @@ public class HeadsetPhoneStateTest { mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE)); - verify(mTelephonyManager).clearSignalStrengthUpdateRequest( + verify(mTelephonyManager, times(2)).clearSignalStrengthUpdateRequest( any(SignalStrengthUpdateRequest.class)); } @@ -181,7 +183,7 @@ public class HeadsetPhoneStateTest { mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); - verify(mTelephonyManager).clearSignalStrengthUpdateRequest( + verify(mTelephonyManager, times(2)).clearSignalStrengthUpdateRequest( any(SignalStrengthUpdateRequest.class)); } @@ -210,7 +212,7 @@ public class HeadsetPhoneStateTest { // Disabling updates from second device should cancel subscription mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); - verify(mTelephonyManager).clearSignalStrengthUpdateRequest( + verify(mTelephonyManager, times(2)).clearSignalStrengthUpdateRequest( any(SignalStrengthUpdateRequest.class)); } @@ -226,6 +228,8 @@ public class HeadsetPhoneStateTest { // Partially enabling updates from first device should trigger partial subscription mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + verify(mTelephonyManager).clearSignalStrengthUpdateRequest( + any(SignalStrengthUpdateRequest.class)); verifyNoMoreInteractions(mTelephonyManager); // Partially enabling updates from second device should trigger partial subscription mHeadsetPhoneState.listenForPhoneState(device2, @@ -244,7 +248,7 @@ public class HeadsetPhoneStateTest { // Partially disabling updates from second device should cancel subscription mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE); verify(mTelephonyManager, times(3)).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); - verify(mTelephonyManager, times(3)).clearSignalStrengthUpdateRequest( + verify(mTelephonyManager, times(4)).clearSignalStrengthUpdateRequest( any(SignalStrengthUpdateRequest.class)); } } -- GitLab From 840ad63e55c2b455507086a983dda8c9490daf83 Mon Sep 17 00:00:00 2001 From: Etienne Ruffieux Date: Thu, 4 Aug 2022 09:31:32 -0700 Subject: [PATCH 010/998] Fixed service / framework test suites broken after migration Bluetooth/service and Bluetooth/framework test suites were broken after the migration of the files from frameworks. The Bluetooth/framework test suite is split between stress tests that require a physical Bluetooth device to run and the unit tests that test the framework hidden APIs. Both framework unit tests and service tests are signed and can be added to the MTS. Test: atest GoogleFrameworkBluetoothTests Test: atest GoogleServiceBluetoothTests Bug: 232577476 Tag: #feature Ignore-AOSP-First: Module only Change-Id: Ibdb20c650db2483c4569d979da5e024e0e95284c --- .../bluetooth/BluetoothCodecStatusTest.java | 493 ------------------ .../bluetooth/le/AdvertiseDataTest.java | 144 ----- .../android/bluetooth/le/ScanFilterTest.java | 215 -------- .../android/bluetooth/le/ScanResultTest.java | 56 -- framework/tests/{ => stress}/Android.bp | 3 +- .../tests/{ => stress}/AndroidManifest.xml | 2 +- framework/tests/{ => stress}/AndroidTest.xml | 6 - .../bluetooth/BluetoothInstrumentation.java | 4 +- .../bluetooth/BluetoothRebootStressTest.java | 0 .../bluetooth/BluetoothStressTest.java | 0 .../bluetooth/BluetoothTestRunner.java | 4 +- .../android/bluetooth/BluetoothTestUtils.java | 59 ++- framework/tests/unit/Android.bp | 33 ++ framework/tests/unit/AndroidManifest.xml | 26 + framework/tests/unit/AndroidTest.xml | 36 ++ .../bluetooth/BluetoothCodecConfigTest.java | 233 +++++---- .../bluetooth/BluetoothCodecStatusTest.java | 490 +++++++++++++++++ .../BluetoothLeAudioCodecConfigTest.java | 0 .../android/bluetooth/BluetoothUuidTest.java | 3 - .../android/bluetooth/le/ScanRecordTest.java | 23 +- .../bluetooth/le/ScanSettingsTest.java | 10 +- service/tests/Android.bp | 19 +- service/tests/AndroidManifest.xml | 5 +- service/tests/AndroidTest.xml | 2 +- 24 files changed, 766 insertions(+), 1100 deletions(-) delete mode 100644 framework/tests/src/android/bluetooth/BluetoothCodecStatusTest.java delete mode 100644 framework/tests/src/android/bluetooth/le/AdvertiseDataTest.java delete mode 100644 framework/tests/src/android/bluetooth/le/ScanFilterTest.java delete mode 100644 framework/tests/src/android/bluetooth/le/ScanResultTest.java rename framework/tests/{ => stress}/Android.bp (87%) rename framework/tests/{ => stress}/AndroidManifest.xml (99%) rename framework/tests/{ => stress}/AndroidTest.xml (81%) rename framework/tests/{ => stress}/src/android/bluetooth/BluetoothInstrumentation.java (97%) rename framework/tests/{ => stress}/src/android/bluetooth/BluetoothRebootStressTest.java (100%) rename framework/tests/{ => stress}/src/android/bluetooth/BluetoothStressTest.java (100%) rename framework/tests/{ => stress}/src/android/bluetooth/BluetoothTestRunner.java (100%) rename framework/tests/{ => stress}/src/android/bluetooth/BluetoothTestUtils.java (97%) create mode 100644 framework/tests/unit/Android.bp create mode 100644 framework/tests/unit/AndroidManifest.xml create mode 100644 framework/tests/unit/AndroidTest.xml rename framework/tests/{ => unit}/src/android/bluetooth/BluetoothCodecConfigTest.java (68%) create mode 100644 framework/tests/unit/src/android/bluetooth/BluetoothCodecStatusTest.java rename framework/tests/{ => unit}/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java (100%) rename framework/tests/{ => unit}/src/android/bluetooth/BluetoothUuidTest.java (93%) rename framework/tests/{ => unit}/src/android/bluetooth/le/ScanRecordTest.java (85%) rename framework/tests/{ => unit}/src/android/bluetooth/le/ScanSettingsTest.java (87%) diff --git a/framework/tests/src/android/bluetooth/BluetoothCodecStatusTest.java b/framework/tests/src/android/bluetooth/BluetoothCodecStatusTest.java deleted file mode 100644 index 1cb2dcae865..00000000000 --- a/framework/tests/src/android/bluetooth/BluetoothCodecStatusTest.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Unit test cases for {@link BluetoothCodecStatus}. - *

- * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java - */ -public class BluetoothCodecStatusTest extends TestCase { - - // Codec configs: A and B are same; C is different - private static final BluetoothCodecConfig config_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - // Local capabilities: A and B are same; C is different - private static final BluetoothCodecConfig local_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - private static final BluetoothCodecConfig local_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - // Selectable capabilities: A and B are same; C is different - private static final BluetoothCodecConfig selectable_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final List LOCAL_CAPABILITY_A = - new ArrayList() {{ - add(local_capability1_A); - add(local_capability2_A); - add(local_capability3_A); - add(local_capability4_A); - add(local_capability5_A); - }}; - - private static final List LOCAL_CAPABILITY_B = - new ArrayList() {{ - add(local_capability1_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability4_B); - add(local_capability5_B); - }}; - - private static final List LOCAL_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(local_capability5_B); - add(local_capability4_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability1_B); - }}; - - private static final List LOCAL_CAPABILITY_C = - new ArrayList() {{ - add(local_capability1_C); - add(local_capability2_C); - add(local_capability3_C); - add(local_capability4_C); - add(local_capability5_C); - }}; - - private static final List SELECTABLE_CAPABILITY_A = - new ArrayList() {{ - add(selectable_capability1_A); - add(selectable_capability2_A); - add(selectable_capability3_A); - add(selectable_capability4_A); - add(selectable_capability5_A); - }}; - - private static final List SELECTABLE_CAPABILITY_B = - new ArrayList() {{ - add(selectable_capability1_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability4_B); - add(selectable_capability5_B); - }}; - - private static final List SELECTABLE_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(selectable_capability5_B); - add(selectable_capability4_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability1_B); - }}; - - private static final List SELECTABLE_CAPABILITY_C = - new ArrayList() {{ - add(selectable_capability1_C); - add(selectable_capability2_C); - add(selectable_capability3_C); - add(selectable_capability4_C); - add(selectable_capability5_C); - }}; - - private static final BluetoothCodecStatus bcs_A = - new BluetoothCodecStatus(config_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A); - private static final BluetoothCodecStatus bcs_B = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B); - private static final BluetoothCodecStatus bcs_B_reordered = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B_REORDERED, - SELECTABLE_CAPABILITY_B_REORDERED); - private static final BluetoothCodecStatus bcs_C = - new BluetoothCodecStatus(config_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C); - - @SmallTest - public void testBluetoothCodecStatus_get_methods() { - - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_A)); - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B)); - assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C)); - - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C)); - - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_C)); - } - - @SmallTest - public void testBluetoothCodecStatus_equals() { - assertTrue(bcs_A.equals(bcs_B)); - assertTrue(bcs_B.equals(bcs_A)); - assertTrue(bcs_A.equals(bcs_B_reordered)); - assertTrue(bcs_B_reordered.equals(bcs_A)); - assertFalse(bcs_A.equals(bcs_C)); - assertFalse(bcs_C.equals(bcs_A)); - } - - private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, - int codecPriority, int sampleRate, int bitsPerSample, int channelMode, - long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { - return new BluetoothCodecConfig.Builder() - .setCodecType(sourceCodecType) - .setCodecPriority(codecPriority) - .setSampleRate(sampleRate) - .setBitsPerSample(bitsPerSample) - .setChannelMode(channelMode) - .setCodecSpecific1(codecSpecific1) - .setCodecSpecific2(codecSpecific2) - .setCodecSpecific3(codecSpecific3) - .setCodecSpecific4(codecSpecific4) - .build(); - - } -} diff --git a/framework/tests/src/android/bluetooth/le/AdvertiseDataTest.java b/framework/tests/src/android/bluetooth/le/AdvertiseDataTest.java deleted file mode 100644 index e58d905357e..00000000000 --- a/framework/tests/src/android/bluetooth/le/AdvertiseDataTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link AdvertiseData}. - *

- * To run the test, use adb shell am instrument -e class 'android.bluetooth.le.AdvertiseDataTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class AdvertiseDataTest extends TestCase { - - private AdvertiseData.Builder mAdvertiseDataBuilder; - - @Override - protected void setUp() throws Exception { - mAdvertiseDataBuilder = new AdvertiseData.Builder(); - } - - @SmallTest - public void testEmptyData() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceUuid() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.setIncludeDeviceName(true).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyManufacturerData() { - Parcel parcel = Parcel.obtain(); - int manufacturerId = 50; - byte[] manufacturerData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addManufacturerData(manufacturerId, manufacturerData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceUuid() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testManufacturerData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - int manufacturerId = 50; - byte[] manufacturerData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2) - .addManufacturerData(manufacturerId, manufacturerData).build(); - - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } -} diff --git a/framework/tests/src/android/bluetooth/le/ScanFilterTest.java b/framework/tests/src/android/bluetooth/le/ScanFilterTest.java deleted file mode 100644 index 35da4bceb62..00000000000 --- a/framework/tests/src/android/bluetooth/le/ScanFilterTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scan filters. - *

- * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanFilterTest extends TestCase { - - private static final String DEVICE_MAC = "01:02:03:04:05:AB"; - private ScanResult mScanResult; - private ScanFilter.Builder mFilterBuilder; - - @Override - protected void setUp() throws Exception { - byte[] scanRecord = new byte[] { - 0x02, 0x01, 0x1a, // advertising flags - 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // setName - 0x02, 0x0A, (byte) 0xec, // tx power level - 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data - 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data - 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble - }; - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); - mScanResult = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), - -10, 1397545200000000L); - mFilterBuilder = new ScanFilter.Builder(); - } - - @SmallTest - public void testsetNameFilter() { - ScanFilter filter = mFilterBuilder.setDeviceName("Ped").build(); - assertTrue("setName filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceName("Pem").build(); - assertFalse("setName filter fails", filter.matches(mScanResult)); - - } - - @SmallTest - public void testDeviceFilter() { - ScanFilter filter = mFilterBuilder.setDeviceAddress(DEVICE_MAC).build(); - assertTrue("device filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - assertFalse("device filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceUuidFilter() { - ScanFilter filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - assertFalse("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder - .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) - .build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceDataFilter() { - byte[] setServiceData = new byte[] { - 0x50, 0x64 }; - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - ScanFilter filter = mFilterBuilder.setServiceData(serviceDataUuid, setServiceData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setServiceData(serviceDataUuid, emptyData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x50 }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, prefixData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] nonMatchData = new byte[] { - 0x51, 0x64 }; - byte[] mask = new byte[] { - (byte) 0x00, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData, mask).build(); - assertTrue("partial service data filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData).build(); - assertFalse("service data filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testManufacturerSpecificData() { - byte[] setManufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - ScanFilter filter = - mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setManufacturerData(manufacturerId, emptyData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x02 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, prefixData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - // Test data mask - byte[] nonMatchData = new byte[] { - 0x02, 0x14 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build(); - assertFalse("manufacturer data filter fails", filter.matches(mScanResult)); - byte[] mask = new byte[] { - (byte) 0xFF, (byte) 0x00 - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build(); - assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testReadWriteParcel() { - ScanFilter filter = mFilterBuilder.build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceName("Ped").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceData = new byte[] { - 0x50, 0x64 }; - - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask) - .build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData, - manufacturerDataMask).build(); - testReadWriteParcelForFilter(filter); - } - - private void testReadWriteParcelForFilter(ScanFilter filter) { - Parcel parcel = Parcel.obtain(); - filter.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - ScanFilter filterFromParcel = - ScanFilter.CREATOR.createFromParcel(parcel); - assertEquals(filter, filterFromParcel); - } -} diff --git a/framework/tests/src/android/bluetooth/le/ScanResultTest.java b/framework/tests/src/android/bluetooth/le/ScanResultTest.java deleted file mode 100644 index 01d5c593bf2..00000000000 --- a/framework/tests/src/android/bluetooth/le/ScanResultTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scans. - *

- * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanResultTest extends TestCase { - - /** - * Test read and write parcel of ScanResult - */ - @SmallTest - public void testScanResultParceling() { - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - "01:02:03:04:05:06"); - byte[] scanRecord = new byte[] { - 1, 2, 3 }; - int rssi = -10; - long timestampMicros = 10000L; - - ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi, - timestampMicros); - Parcel parcel = Parcel.obtain(); - result.writeToParcel(parcel, 0); - // Need to reset parcel data position to the beginning. - parcel.setDataPosition(0); - ScanResult resultFromParcel = ScanResult.CREATOR.createFromParcel(parcel); - assertEquals(result, resultFromParcel); - } - -} diff --git a/framework/tests/Android.bp b/framework/tests/stress/Android.bp similarity index 87% rename from framework/tests/Android.bp rename to framework/tests/stress/Android.bp index efec1dd5210..f176be001c5 100644 --- a/framework/tests/Android.bp +++ b/framework/tests/stress/Android.bp @@ -21,11 +21,12 @@ android_test { "android.test.base", ], static_libs: [ + "androidx.test.rules", "junit", "modules-utils-bytesmatcher", ], test_suites: [ "general-tests", - "mts-bluetooth", ], + certificate: ":com.android.bluetooth.certificate", } diff --git a/framework/tests/AndroidManifest.xml b/framework/tests/stress/AndroidManifest.xml similarity index 99% rename from framework/tests/AndroidManifest.xml rename to framework/tests/stress/AndroidManifest.xml index 75583d5298c..e71b8763ec2 100644 --- a/framework/tests/AndroidManifest.xml +++ b/framework/tests/stress/AndroidManifest.xml @@ -44,4 +44,4 @@ android:targetPackage="com.android.bluetooth.tests" android:label="Bluetooth Test Utils" /> - + \ No newline at end of file diff --git a/framework/tests/AndroidTest.xml b/framework/tests/stress/AndroidTest.xml similarity index 81% rename from framework/tests/AndroidTest.xml rename to framework/tests/stress/AndroidTest.xml index ed89c16eced..f93c4ebf5bf 100644 --- a/framework/tests/AndroidTest.xml +++ b/framework/tests/stress/AndroidTest.xml @@ -29,10 +29,4 @@

diff --git a/framework/tests/src/android/bluetooth/BluetoothTestUtils.java b/framework/tests/stress/src/android/bluetooth/BluetoothTestUtils.java similarity index 97% rename from framework/tests/src/android/bluetooth/BluetoothTestUtils.java rename to framework/tests/stress/src/android/bluetooth/BluetoothTestUtils.java index 12183f8f02d..41e1cd51a21 100644 --- a/framework/tests/src/android/bluetooth/BluetoothTestUtils.java +++ b/framework/tests/stress/src/android/bluetooth/BluetoothTestUtils.java @@ -16,8 +16,6 @@ package android.bluetooth; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -69,7 +67,7 @@ public class BluetoothTestUtils extends Assert { private int mFiredFlags = 0; private long mCompletedTime = -1; - public FlagReceiver(int expectedFlags) { + FlagReceiver(int expectedFlags) { mExpectedFlags = expectedFlags; } @@ -108,7 +106,7 @@ public class BluetoothTestUtils extends Assert { private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9; private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10; - public BluetoothReceiver(int expectedFlags) { + BluetoothReceiver(int expectedFlags) { super(expectedFlags); } @@ -162,7 +160,7 @@ public class BluetoothTestUtils extends Assert { private int mPasskey; private byte[] mPin; - public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { + PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { super(expectedFlags); mDevice = device; @@ -221,7 +219,7 @@ public class BluetoothTestUtils extends Assert { private int mProfile; private String mConnectionAction; - public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { + ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { super(expectedFlags); mDevice = device; @@ -278,7 +276,7 @@ public class BluetoothTestUtils extends Assert { private class ConnectPanReceiver extends ConnectProfileReceiver { private int mRole; - public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { + ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { super(device, BluetoothProfile.PAN, expectedFlags); mRole = role; @@ -298,7 +296,7 @@ public class BluetoothTestUtils extends Assert { private static final int STATE_CONNECTED_FLAG = 1; private static final int STATE_DISCONNECTED_FLAG = 1 << 1; - public StartStopScoReceiver(int expectedFlags) { + StartStopScoReceiver(int expectedFlags) { super(expectedFlags); } @@ -325,7 +323,7 @@ public class BluetoothTestUtils extends Assert { private static final int MESSAGE_RECEIVED_FLAG = 1; private static final int STATUS_CHANGED_FLAG = 1 << 1; - public MceSetMessageStatusReceiver(int expectedFlags) { + MceSetMessageStatusReceiver(int expectedFlags) { super(expectedFlags); } @@ -336,12 +334,16 @@ public class BluetoothTestUtils extends Assert { assertNotNull(handle); setFiredFlag(MESSAGE_RECEIVED_FLAG); mMsgHandle = handle; - } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED + .equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, + BluetoothMapClient.RESULT_FAILURE); assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); setFiredFlag(STATUS_CHANGED_FLAG); - } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED + .equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, + BluetoothMapClient.RESULT_FAILURE); assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); setFiredFlag(STATUS_CHANGED_FLAG); } @@ -1001,13 +1003,13 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.STATE_DISCONNECTING: start = System.currentTimeMillis(); if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).connect(device)); + assertTrue(((BluetoothA2dp) proxy).connect(device)); } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).connect(device)); + assertTrue(((BluetoothHeadset) proxy).connect(device)); } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).connect(device)); + assertTrue(((BluetoothHidHost) proxy).connect(device)); } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).connect(device)); + assertTrue(((BluetoothMapClient) proxy).connect(device)); } break; default: @@ -1078,13 +1080,13 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.STATE_CONNECTING: start = System.currentTimeMillis(); if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).disconnect(device)); + assertTrue(((BluetoothA2dp) proxy).disconnect(device)); } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).disconnect(device)); + assertTrue(((BluetoothHeadset) proxy).disconnect(device)); } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).disconnect(device)); + assertTrue(((BluetoothHidHost) proxy).disconnect(device)); } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).disconnect(device)); + assertTrue(((BluetoothMapClient) proxy).disconnect(device)); } break; case BluetoothProfile.STATE_DISCONNECTED: @@ -1161,8 +1163,8 @@ public class BluetoothTestUtils extends Assert { if (connect) { methodName = String.format("connectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | - ConnectProfileReceiver.STATE_CONNECTING_FLAG); + mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG + | ConnectProfileReceiver.STATE_CONNECTING_FLAG); role = BluetoothPan.LOCAL_PANU_ROLE; } else { methodName = String.format("incomingPanConnection(device=%s)", device); @@ -1266,8 +1268,8 @@ public class BluetoothTestUtils extends Assert { if (disconnect) { methodName = String.format("disconnectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | - ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); + mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG + | ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); role = BluetoothPan.LOCAL_PANU_ROLE; } else { methodName = String.format("incomingPanDisconnection(device=%s)", device); @@ -1478,7 +1480,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask)); + methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, + firedFlags, mask)); } /** @@ -1522,7 +1525,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask)); + methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, + firedFlags, mask)); } private void addReceiver(BroadcastReceiver receiver, String[] actions) { @@ -1619,6 +1623,7 @@ public class BluetoothTestUtils extends Assert { if (mPan != null) { return mPan; } + break; case BluetoothProfile.MAP_CLIENT: if (mMce != null) { return mMce; diff --git a/framework/tests/unit/Android.bp b/framework/tests/unit/Android.bp new file mode 100644 index 00000000000..77911023959 --- /dev/null +++ b/framework/tests/unit/Android.bp @@ -0,0 +1,33 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "FrameworkBluetoothTests", + + defaults: ["framework-bluetooth-tests-defaults"], + + min_sdk_version: "current", + target_sdk_version: "current", + + // Include all test java files. + srcs: ["src/**/*.java"], + jacoco: { + include_filter: ["android.bluetooth.*"], + exclude_filter: [], + }, + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "androidx.test.rules", + "junit", + "modules-utils-bytesmatcher", + ], + test_suites: [ + "general-tests", + "mts-bluetooth", + ], + certificate: ":com.android.bluetooth.certificate", +} diff --git a/framework/tests/unit/AndroidManifest.xml b/framework/tests/unit/AndroidManifest.xml new file mode 100644 index 00000000000..114ceeb9be2 --- /dev/null +++ b/framework/tests/unit/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/framework/tests/unit/AndroidTest.xml b/framework/tests/unit/AndroidTest.xml new file mode 100644 index 00000000000..b8ca07a2899 --- /dev/null +++ b/framework/tests/unit/AndroidTest.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/framework/tests/src/android/bluetooth/BluetoothCodecConfigTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java similarity index 68% rename from framework/tests/src/android/bluetooth/BluetoothCodecConfigTest.java rename to framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java index 53623b809c8..cb059aeb430 100644 --- a/framework/tests/src/android/bluetooth/BluetoothCodecConfigTest.java +++ b/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java @@ -22,161 +22,160 @@ import junit.framework.TestCase; /** * Unit test cases for {@link BluetoothCodecConfig}. - *

- * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java */ public class BluetoothCodecConfigTest extends TestCase { - private static final int[] kCodecTypeArray = new int[] { - BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, - }; - private static final int[] kCodecPriorityArray = new int[] { - BluetoothCodecConfig.CODEC_PRIORITY_DISABLED, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, - }; - private static final int[] kSampleRateArray = new int[] { - BluetoothCodecConfig.SAMPLE_RATE_NONE, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.SAMPLE_RATE_88200, - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.SAMPLE_RATE_176400, - BluetoothCodecConfig.SAMPLE_RATE_192000, - }; - private static final int[] kBitsPerSampleArray = new int[] { - BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - }; - private static final int[] kChannelModeArray = new int[] { - BluetoothCodecConfig.CHANNEL_MODE_NONE, - BluetoothCodecConfig.CHANNEL_MODE_MONO, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - }; - private static final long[] kCodecSpecific1Array = new long[] { - 1000, - 1001, - 1002, - 1003, - }; - private static final long[] kCodecSpecific2Array = new long[] { - 2000, - 2001, - 2002, - 2003, - }; - private static final long[] kCodecSpecific3Array = new long[] { - 3000, - 3001, - 3002, - 3003, - }; - private static final long[] kCodecSpecific4Array = new long[] { - 4000, - 4001, - 4002, - 4003, - }; - - private static final int kTotalConfigs = kCodecTypeArray.length * kCodecPriorityArray.length - * kSampleRateArray.length * kBitsPerSampleArray.length * kChannelModeArray.length - * kCodecSpecific1Array.length * kCodecSpecific2Array.length * kCodecSpecific3Array.length - * kCodecSpecific4Array.length; - - private int selectCodecType(int configId) { - int left = kCodecTypeArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecTypeArray.length; - return kCodecTypeArray[index]; - } + private static final int[] sCodecTypeArray = new int[] { + BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, + }; + private static final int[] sCodecPriorityArray = new int[] { + BluetoothCodecConfig.CODEC_PRIORITY_DISABLED, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, + }; + private static final int[] sSampleRateArray = new int[] { + BluetoothCodecConfig.SAMPLE_RATE_NONE, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.SAMPLE_RATE_88200, + BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.SAMPLE_RATE_176400, + BluetoothCodecConfig.SAMPLE_RATE_192000, + }; + private static final int[] sBitsPerSampleArray = new int[] { + BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.BITS_PER_SAMPLE_32, + }; + private static final int[] sChannelModeArray = new int[] { + BluetoothCodecConfig.CHANNEL_MODE_NONE, + BluetoothCodecConfig.CHANNEL_MODE_MONO, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + }; + private static final long[] sCodecSpecific1Array = new long[] { + 1000, + 1001, + 1002, + 1003, + }; + private static final long[] sCodecSpecific2Array = new long[] { + 2000, + 2001, + 2002, + 2003, + }; + private static final long[] sCodecSpecific3Array = new long[] { + 3000, + 3001, + 3002, + 3003, + }; + private static final long[] sCodecSpecific4Array = new long[] { + 4000, + 4001, + 4002, + 4003, + }; + + private static final int sTotalConfigs = sCodecTypeArray.length * sCodecPriorityArray.length + * sSampleRateArray.length * sBitsPerSampleArray.length * sChannelModeArray.length + * sCodecSpecific1Array.length * sCodecSpecific2Array.length + * sCodecSpecific3Array.length * sCodecSpecific4Array.length; + + private int selectCodecType(int configId) { + int left = sCodecTypeArray.length; + int right = sTotalConfigs / left; + int index = configId / right; + index = index % sCodecTypeArray.length; + return sCodecTypeArray[index]; + } private int selectCodecPriority(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kCodecPriorityArray.length; - return kCodecPriorityArray[index]; + index = index % sCodecPriorityArray.length; + return sCodecPriorityArray[index]; } private int selectSampleRate(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kSampleRateArray.length; - return kSampleRateArray[index]; + index = index % sSampleRateArray.length; + return sSampleRateArray[index]; } private int selectBitsPerSample(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kBitsPerSampleArray.length; - return kBitsPerSampleArray[index]; + index = index % sBitsPerSampleArray.length; + return sBitsPerSampleArray[index]; } private int selectChannelMode(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length * sChannelModeArray.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kChannelModeArray.length; - return kChannelModeArray[index]; + index = index % sChannelModeArray.length; + return sChannelModeArray[index]; } private long selectCodecSpecific1(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length * sChannelModeArray.length + * sCodecSpecific1Array.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kCodecSpecific1Array.length; - return kCodecSpecific1Array[index]; + index = index % sCodecSpecific1Array.length; + return sCodecSpecific1Array[index]; } private long selectCodecSpecific2(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length * sChannelModeArray.length + * sCodecSpecific1Array.length * sCodecSpecific2Array.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kCodecSpecific2Array.length; - return kCodecSpecific2Array[index]; + index = index % sCodecSpecific2Array.length; + return sCodecSpecific2Array[index]; } private long selectCodecSpecific3(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length * sChannelModeArray.length + * sCodecSpecific1Array.length * sCodecSpecific2Array.length + * sCodecSpecific3Array.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kCodecSpecific3Array.length; - return kCodecSpecific3Array[index]; + index = index % sCodecSpecific3Array.length; + return sCodecSpecific3Array[index]; } private long selectCodecSpecific4(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length * - kCodecSpecific4Array.length; - int right = kTotalConfigs / left; + int left = sCodecTypeArray.length * sCodecPriorityArray.length * sSampleRateArray.length + * sBitsPerSampleArray.length * sChannelModeArray.length + * sCodecSpecific1Array.length * sCodecSpecific2Array.length + * sCodecSpecific3Array.length * sCodecSpecific4Array.length; + int right = sTotalConfigs / left; int index = configId / right; - index = index % kCodecSpecific4Array.length; - return kCodecSpecific4Array[index]; + index = index % sCodecSpecific4Array.length; + return sCodecSpecific4Array[index]; } @SmallTest public void testBluetoothCodecConfig_valid_get_methods() { - for (int config_id = 0; config_id < kTotalConfigs; config_id++) { + for (int config_id = 0; config_id < sTotalConfigs; config_id++) { int codec_type = selectCodecType(config_id); int codec_priority = selectCodecPriority(config_id); int sample_rate = selectSampleRate(config_id); diff --git a/framework/tests/unit/src/android/bluetooth/BluetoothCodecStatusTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothCodecStatusTest.java new file mode 100644 index 00000000000..9c6674e1364 --- /dev/null +++ b/framework/tests/unit/src/android/bluetooth/BluetoothCodecStatusTest.java @@ -0,0 +1,490 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Unit test cases for {@link BluetoothCodecStatus}. + */ +public class BluetoothCodecStatusTest extends TestCase { + + // Codec configs: A and B are same; C is different + private static final BluetoothCodecConfig CONFIG_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig CONFIG_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig CONFIG_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + // Local capabilities: A and B are same; C is different + private static final BluetoothCodecConfig LOCAL_CAPABILITY_1_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_1_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_1_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_2_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_2_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_2_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_3_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_3_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_3_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_4_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_4_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_4_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_5_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_5_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig LOCAL_CAPABILITY_5_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + + // Selectable capabilities: A and B are same; C is different + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_1_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_1_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_1_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_2_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_2_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_2_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_3_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_3_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_3_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_16, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_4_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_4_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_4_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_5_A = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_5_B = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO + | BluetoothCodecConfig.CHANNEL_MODE_MONO, + 1000, 2000, 3000, 4000); + + private static final BluetoothCodecConfig SELECTABE_CAPABILITY_5_C = + buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_44100 + | BluetoothCodecConfig.SAMPLE_RATE_48000 + | BluetoothCodecConfig.SAMPLE_RATE_88200 + | BluetoothCodecConfig.SAMPLE_RATE_96000, + BluetoothCodecConfig.BITS_PER_SAMPLE_16 + | BluetoothCodecConfig.BITS_PER_SAMPLE_24 + | BluetoothCodecConfig.BITS_PER_SAMPLE_32, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 2000, 3000, 4000); + + private static final List LOCAL_CAPABILITY_A = + new ArrayList() {{ + add(LOCAL_CAPABILITY_1_A); + add(LOCAL_CAPABILITY_2_A); + add(LOCAL_CAPABILITY_3_A); + add(LOCAL_CAPABILITY_4_A); + add(LOCAL_CAPABILITY_5_A); + }}; + + private static final List LOCAL_CAPABILITY_B = + new ArrayList() {{ + add(LOCAL_CAPABILITY_1_B); + add(LOCAL_CAPABILITY_2_B); + add(LOCAL_CAPABILITY_3_B); + add(LOCAL_CAPABILITY_4_B); + add(LOCAL_CAPABILITY_5_B); + }}; + + private static final List LOCAL_CAPABILITY_B_REORDERED = + new ArrayList() {{ + add(LOCAL_CAPABILITY_5_B); + add(LOCAL_CAPABILITY_4_B); + add(LOCAL_CAPABILITY_2_B); + add(LOCAL_CAPABILITY_3_B); + add(LOCAL_CAPABILITY_1_B); + }}; + + private static final List LOCAL_CAPABILITY_C = + new ArrayList() {{ + add(LOCAL_CAPABILITY_1_C); + add(LOCAL_CAPABILITY_2_C); + add(LOCAL_CAPABILITY_3_C); + add(LOCAL_CAPABILITY_4_C); + add(LOCAL_CAPABILITY_5_C); + }}; + + private static final List SELECTABLE_CAPABILITY_A = + new ArrayList() {{ + add(SELECTABE_CAPABILITY_1_A); + add(SELECTABE_CAPABILITY_2_A); + add(SELECTABE_CAPABILITY_3_A); + add(SELECTABE_CAPABILITY_4_A); + add(SELECTABE_CAPABILITY_5_A); + }}; + + private static final List SELECTABLE_CAPABILITY_B = + new ArrayList() {{ + add(SELECTABE_CAPABILITY_1_B); + add(SELECTABE_CAPABILITY_2_B); + add(SELECTABE_CAPABILITY_3_B); + add(SELECTABE_CAPABILITY_4_B); + add(SELECTABE_CAPABILITY_5_B); + }}; + + private static final List SELECTABLE_CAPABILITY_B_REORDERED = + new ArrayList() {{ + add(SELECTABE_CAPABILITY_5_B); + add(SELECTABE_CAPABILITY_4_B); + add(SELECTABE_CAPABILITY_2_B); + add(SELECTABE_CAPABILITY_3_B); + add(SELECTABE_CAPABILITY_1_B); + }}; + + private static final List SELECTABLE_CAPABILITY_C = + new ArrayList() {{ + add(SELECTABE_CAPABILITY_1_C); + add(SELECTABE_CAPABILITY_2_C); + add(SELECTABE_CAPABILITY_3_C); + add(SELECTABE_CAPABILITY_4_C); + add(SELECTABE_CAPABILITY_5_C); + }}; + + private static final BluetoothCodecStatus BCS_A = + new BluetoothCodecStatus(CONFIG_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A); + private static final BluetoothCodecStatus BCS_B = + new BluetoothCodecStatus(CONFIG_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B); + private static final BluetoothCodecStatus BCS_B_REORDERED = + new BluetoothCodecStatus(CONFIG_B, LOCAL_CAPABILITY_B_REORDERED, + SELECTABLE_CAPABILITY_B_REORDERED); + private static final BluetoothCodecStatus BCS_C = + new BluetoothCodecStatus(CONFIG_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C); + + @SmallTest + public void testBluetoothCodecStatus_get_methods() { + + assertTrue(Objects.equals(BCS_A.getCodecConfig(), CONFIG_A)); + assertTrue(Objects.equals(BCS_A.getCodecConfig(), CONFIG_B)); + assertFalse(Objects.equals(BCS_A.getCodecConfig(), CONFIG_C)); + + assertTrue(BCS_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A)); + assertTrue(BCS_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B)); + assertFalse(BCS_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C)); + + assertTrue(BCS_A.getCodecsSelectableCapabilities() + .equals(SELECTABLE_CAPABILITY_A)); + assertTrue(BCS_A.getCodecsSelectableCapabilities() + .equals(SELECTABLE_CAPABILITY_B)); + assertFalse(BCS_A.getCodecsSelectableCapabilities() + .equals(SELECTABLE_CAPABILITY_C)); + } + + @SmallTest + public void testBluetoothCodecStatus_equals() { + assertTrue(BCS_A.equals(BCS_B)); + assertTrue(BCS_B.equals(BCS_A)); + assertTrue(BCS_A.equals(BCS_B_REORDERED)); + assertTrue(BCS_B_REORDERED.equals(BCS_A)); + assertFalse(BCS_A.equals(BCS_C)); + assertFalse(BCS_C.equals(BCS_A)); + } + + private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, + int codecPriority, int sampleRate, int bitsPerSample, int channelMode, + long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { + return new BluetoothCodecConfig.Builder() + .setCodecType(sourceCodecType) + .setCodecPriority(codecPriority) + .setSampleRate(sampleRate) + .setBitsPerSample(bitsPerSample) + .setChannelMode(channelMode) + .setCodecSpecific1(codecSpecific1) + .setCodecSpecific2(codecSpecific2) + .setCodecSpecific3(codecSpecific3) + .setCodecSpecific4(codecSpecific4) + .build(); + + } +} diff --git a/framework/tests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java similarity index 100% rename from framework/tests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java rename to framework/tests/unit/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java diff --git a/framework/tests/src/android/bluetooth/BluetoothUuidTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothUuidTest.java similarity index 93% rename from framework/tests/src/android/bluetooth/BluetoothUuidTest.java rename to framework/tests/unit/src/android/bluetooth/BluetoothUuidTest.java index 536d722679b..514a5845f13 100644 --- a/framework/tests/src/android/bluetooth/BluetoothUuidTest.java +++ b/framework/tests/unit/src/android/bluetooth/BluetoothUuidTest.java @@ -23,9 +23,6 @@ import junit.framework.TestCase; /** * Unit test cases for {@link BluetoothUuid}. - *

- * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothUuidTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ public class BluetoothUuidTest extends TestCase { diff --git a/framework/tests/src/android/bluetooth/le/ScanRecordTest.java b/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java similarity index 85% rename from framework/tests/src/android/bluetooth/le/ScanRecordTest.java rename to framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java index 76f56156479..b37f80593a2 100644 --- a/framework/tests/src/android/bluetooth/le/ScanRecordTest.java +++ b/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java @@ -39,15 +39,24 @@ public class ScanRecordTest extends TestCase { /** * Example raw beacons captured from a Blue Charm BC011 */ - private static final String RECORD_URL = "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_UUID = "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_TLM = "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000"; - private static final String RECORD_IBEACON = "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000"; + private static final String RECORD_URL = + "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730" + + "009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; + private static final String RECORD_UUID = + "0201060303AAFE1716AAFE00EE626C7565636861726D3100000000000100000" + + "9168020691E0EFE13551109426C7565436861726D5F313639363835000000"; + private static final String RECORD_TLM = + "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080" + + "EFE13551109426C7565436861726D5F313639363835000000000000000000"; + private static final String RECORD_IBEACON = + "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C5091" + + "68020691E0EFE13551109426C7565436861726D5F31363936383500000000"; /** * Example Eddystone E2EE-EID beacon from design doc */ - private static final String RECORD_E2EE_EID = "0201061816AAFE400000000000000000000000000000000000000000"; + private static final String RECORD_E2EE_EID = + "0201061816AAFE400000000000000000000000000000000000000000"; @SmallTest public void testMatchesAnyField_Eddystone_Parser() { @@ -148,8 +157,8 @@ public class ScanRecordTest extends TestCase { // Assert two byte arrays are equal. private static void assertArrayEquals(byte[] expected, byte[] actual) { if (!Arrays.equals(expected, actual)) { - fail("expected:<" + Arrays.toString(expected) + - "> but was:<" + Arrays.toString(actual) + ">"); + fail("expected:<" + Arrays.toString(expected) + + "> but was:<" + Arrays.toString(actual) + ">"); } } diff --git a/framework/tests/src/android/bluetooth/le/ScanSettingsTest.java b/framework/tests/unit/src/android/bluetooth/le/ScanSettingsTest.java similarity index 87% rename from framework/tests/src/android/bluetooth/le/ScanSettingsTest.java rename to framework/tests/unit/src/android/bluetooth/le/ScanSettingsTest.java index 7c42c3b4677..180c3be4198 100644 --- a/framework/tests/src/android/bluetooth/le/ScanSettingsTest.java +++ b/framework/tests/unit/src/android/bluetooth/le/ScanSettingsTest.java @@ -43,8 +43,8 @@ public class ScanSettingsTest extends TestCase { try { builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH); + ScanSettings.CALLBACK_TYPE_ALL_MATCHES + | ScanSettings.CALLBACK_TYPE_FIRST_MATCH); fail("should have thrown IllegalArgumentException!"); } catch (IllegalArgumentException e) { // nothing to do @@ -52,9 +52,9 @@ public class ScanSettingsTest extends TestCase { try { builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH | - ScanSettings.CALLBACK_TYPE_MATCH_LOST); + ScanSettings.CALLBACK_TYPE_ALL_MATCHES + | ScanSettings.CALLBACK_TYPE_FIRST_MATCH + | ScanSettings.CALLBACK_TYPE_MATCH_LOST); fail("should have thrown IllegalArgumentException!"); } catch (IllegalArgumentException e) { // nothing to do diff --git a/service/tests/Android.bp b/service/tests/Android.bp index c26df884bf3..3b5e8662620 100644 --- a/service/tests/Android.bp +++ b/service/tests/Android.bp @@ -17,18 +17,6 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -filegroup { - name: "service-bluetooth-tests-sources", - srcs: [ - "src/**/*.java", - ], - visibility: [ - "//frameworks/base", - "//frameworks/base/services", - "//frameworks/base/services/tests/servicestests", - ], -} - android_test { name: "ServiceBluetoothTests", @@ -42,12 +30,8 @@ android_test { static_libs: [ "androidx.test.rules", - "collector-device-lib", - "hamcrest-library", "mockito-target-extended-minus-junit4", "platform-test-annotations", - "frameworks-base-testutils", - "truth-prebuilt", // Statically link service-bluetooth-pre-jarjar since we want to test the working copy of // service-uwb, not the on-device copy. @@ -68,9 +52,12 @@ android_test { jni_libs: [ // these are needed for Extended Mockito + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", "libbluetooth_jni", ], compile_multilib: "both", + certificate: ":com.android.bluetooth.certificate", min_sdk_version: "current", diff --git a/service/tests/AndroidManifest.xml b/service/tests/AndroidManifest.xml index afecc067ed3..72e2ca0c002 100644 --- a/service/tests/AndroidManifest.xml +++ b/service/tests/AndroidManifest.xml @@ -31,10 +31,9 @@ - - + android:label="Service Bluetooth Tests"/> diff --git a/service/tests/AndroidTest.xml b/service/tests/AndroidTest.xml index c31c6cb0686..1befc15fd5b 100644 --- a/service/tests/AndroidTest.xml +++ b/service/tests/AndroidTest.xml @@ -24,7 +24,7 @@ value="com.google.android.bluetooth.apex" /> -- GitLab From 2ddaea43088cbfd79f41c2b821ef681ea8475734 Mon Sep 17 00:00:00 2001 From: Chienyuan Date: Thu, 21 Jul 2022 17:36:39 +0800 Subject: [PATCH 011/998] Check cached command for ack_pause Bug: 238850064 Test: gd/cert/run Tag: #refactor Change-Id: Iec306c4287ed53fedd920ec694a2e48fa9e0d9ce Merged-In: Iec306c4287ed53fedd920ec694a2e48fa9e0d9ce (cherry picked from commit f438c3d8faa37e1d5c2102c0e503e3a7b28e1044) (cherry picked from commit 162d2b3748252185a10b57ec82f27b4972d2ce4c) --- system/gd/hci/le_address_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/gd/hci/le_address_manager.cc b/system/gd/hci/le_address_manager.cc index 28f51004a78..78a490a6728 100644 --- a/system/gd/hci/le_address_manager.cc +++ b/system/gd/hci/le_address_manager.cc @@ -240,7 +240,7 @@ void LeAddressManager::ack_pause(LeAddressManagerCallback* callback) { } if (address_policy_ != AddressPolicy::POLICY_NOT_SET) { - handle_next_command(); + check_cached_commands(); } } -- GitLab From 3e9249a6c13ac5df8f32d25a1ee88b390105ddb3 Mon Sep 17 00:00:00 2001 From: Stephanie Bak Date: Sat, 7 May 2022 07:49:43 +0000 Subject: [PATCH 012/998] Bluetooth APM enhancement Bug: 239983569 Test: manual Ignore-AOSP-First: merge conflicts in AOSP Change-Id: I2092880e047a1a9d50a47e4724e5fb2815d04f2d --- android/app/res/values/strings.xml | 5 + .../BluetoothAirplaneModeListener.java | 121 ++++++++++++- .../BluetoothDeviceConfigListener.java | 36 +++- .../bluetooth/BluetoothManagerService.java | 94 +++++++++- .../bluetooth/BluetoothModeChangeHelper.java | 71 ++++++++ .../BluetoothNotificationManager.java | 162 ++++++++++++++++++ .../BluetoothAirplaneModeListenerTest.java | 4 +- 7 files changed, 476 insertions(+), 17 deletions(-) create mode 100644 service/java/com/android/server/bluetooth/BluetoothNotificationManager.java diff --git a/android/app/res/values/strings.xml b/android/app/res/values/strings.xml index cc489220baa..7d2bb40d8a3 100644 --- a/android/app/res/values/strings.xml +++ b/android/app/res/values/strings.xml @@ -248,4 +248,9 @@ Bluetooth Audio Files bigger than 4GB cannot be transferred Connect to Bluetooth + You turned on Bluetooth + Your phone will keep Bluetooth on in airplane mode, unless you turn it off while in this mode + Bluetooth stays on + Bluetooth and Wi-Fi stays on + Your phone will keep both on in airplane mode, unless you turn it off while in this mode diff --git a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java index d4aad1c642c..3ff6077c66c 100644 --- a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java +++ b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java @@ -18,6 +18,8 @@ package com.android.server.bluetooth; import android.annotation.RequiresPermission; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.ContentObserver; import android.os.Handler; import android.os.Looper; @@ -41,18 +43,44 @@ public class BluetoothAirplaneModeListener { private static final String TAG = "BluetoothAirplaneModeListener"; @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count"; + // keeps track of whether wifi should remain on in airplane mode + public static final String WIFI_APM_STATE = "wifi_apm_state"; + // keeps track of whether wifi and bt remains on notification was shown + public static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification"; + // keeps track of whether bt remains on notification was shown + public static final String APM_BT_NOTIFICATION = "apm_bt_notification"; + // keeps track of whether airplane mode enhancement feature is enabled + public static final String APM_ENHANCEMENT = "apm_enhancement_enabled"; + // keeps track of whether user changed bt state in airplane mode + public static final String APM_USER_TOGGLED_BLUETOOTH = "apm_user_toggled_bluetooth"; + // keeps track of whether bt should remain on in airplane mode + public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state"; + // keeps track of what the default value for bt should be in airplane mode + public static final String BT_DEFAULT_APM_STATE = "bt_default_apm_state"; + // keeps track of whether user enabling bt notification was shown + public static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification"; + private static final int MSG_AIRPLANE_MODE_CHANGED = 0; + public static final int NOTIFICATION_NOT_SHOWN = 0; + public static final int NOTIFICATION_SHOWN = 1; + public static final int UNUSED = 0; + public static final int USED = 1; @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times private final BluetoothManagerService mBluetoothManager; private final BluetoothAirplaneModeHandler mHandler; + private final Context mContext; private BluetoothModeChangeHelper mAirplaneHelper; + private BluetoothNotificationManager mNotificationManager; @VisibleForTesting int mToastCount = 0; - BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) { + BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context, + BluetoothNotificationManager notificationManager) { mBluetoothManager = service; + mNotificationManager = notificationManager; + mContext = context; mHandler = new BluetoothAirplaneModeHandler(looper); context.getContentResolver().registerContentObserver( @@ -117,8 +145,28 @@ public class BluetoothAirplaneModeListener { // BLUETOOTH_ON_AIRPLANE mode. mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON, BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - if (shouldPopToast()) { - mAirplaneHelper.showToastMessage(); + if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) { + if (shouldPopToast()) { + mAirplaneHelper.showToastMessage(); + } + } else { + if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) { + try { + sendApmNotification("bluetooth_and_wifi_stays_on_title", + "bluetooth_and_wifi_stays_on_message", + APM_WIFI_BT_NOTIFICATION); + } catch (Exception e) { + Log.e(TAG, "APM enhancement BT and Wi-Fi stays on notification not shown"); + } + } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(APM_BT_NOTIFICATION)) { + try { + sendApmNotification("bluetooth_stays_on_title", + "bluetooth_enabled_apm_message", + APM_BT_NOTIFICATION); + } catch (Exception e) { + Log.e(TAG, "APM enhancement BT stays on notification not shown"); + } + } } return; } @@ -132,10 +180,69 @@ public class BluetoothAirplaneModeListener { if (mAirplaneHelper == null) { return false; } - if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() - || !mAirplaneHelper.isMediaProfileConnected()) { - return false; + boolean apmEnhancementUsed = isApmEnhancementEnabled() && isBluetoothToggledOnApm(); + + // APM feature disabled or user has not used the feature yet by changing BT state in APM + // BT will only remain on in APM when media profile is connected + if (!apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() + && mAirplaneHelper.isAirplaneModeOn() + && mAirplaneHelper.isMediaProfileConnected()) { + return true; } - return true; + // APM feature enabled and user has used the feature by changing BT state in APM + // BT will only remain on in APM based on user's last action in APM + if (apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() + && mAirplaneHelper.isAirplaneModeOn() + && mAirplaneHelper.isBluetoothOnAPM()) { + return true; + } + // APM feature enabled and user has not used the feature yet by changing BT state in APM + // BT will only remain on in APM if the default value is set to on + if (isApmEnhancementEnabled() && !isBluetoothToggledOnApm() + && mAirplaneHelper.isBluetoothOn() + && mAirplaneHelper.isAirplaneModeOn() + && mAirplaneHelper.isBluetoothOnAPM()) { + return true; + } + return false; + } + + private boolean isApmEnhancementEnabled() { + return mAirplaneHelper.getSettingsInt(APM_ENHANCEMENT) == 1; + } + + private boolean isBluetoothToggledOnApm() { + return mAirplaneHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED) == USED; + } + + private boolean isWifiEnabledOnApm() { + return mAirplaneHelper.getSettingsInt(Settings.Global.WIFI_ON) != 0 + && mAirplaneHelper.getSettingsSecureInt(WIFI_APM_STATE, 0) == 1; + } + + private boolean isFirstTimeNotification(String name) { + return mAirplaneHelper.getSettingsSecureInt( + name, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; + } + + /** + * Helper method to send APM notification + */ + public void sendApmNotification(String titleId, String messageId, String notificationState) + throws PackageManager.NameNotFoundException { + String btPackageName = mAirplaneHelper.getBluetoothPackageName(); + if (btPackageName == null) { + Log.e(TAG, "Unable to find Bluetooth package name with " + + "APM notification resources"); + return; + } + Resources resources = mContext.getPackageManager() + .getResourcesForApplication(btPackageName); + int title = resources.getIdentifier(titleId, "string", btPackageName); + int message = resources.getIdentifier(messageId, "string", btPackageName); + mNotificationManager.sendApmNotification( + resources.getString(title), resources.getString(message)); + mAirplaneHelper.setSettingsSecureInt(notificationState, + NOTIFICATION_SHOWN); } } diff --git a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java index 62860a5f02a..0c6eac13e20 100644 --- a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java +++ b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java @@ -16,7 +16,12 @@ package com.android.server.bluetooth; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; + +import android.content.Context; import android.provider.DeviceConfig; +import android.provider.Settings; import android.util.Log; import java.util.ArrayList; @@ -35,10 +40,22 @@ public class BluetoothDeviceConfigListener { private final BluetoothManagerService mService; private final boolean mLogDebug; + private final Context mContext; + private static final int DEFAULT_APM_ENHANCEMENT = 0; + private static final int DEFAULT_BT_APM_STATE = 0; + + private boolean mPrevApmEnhancement; + private boolean mPrevBtApmState; - BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { + BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug, + Context context) { mService = service; mLogDebug = logDebug; + mContext = context; + mPrevApmEnhancement = Settings.Global.getInt(mContext.getContentResolver(), + APM_ENHANCEMENT, DEFAULT_APM_ENHANCEMENT) == 1; + mPrevBtApmState = Settings.Global.getInt(mContext.getContentResolver(), + BT_DEFAULT_APM_STATE, DEFAULT_BT_APM_STATE) == 1; DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_BLUETOOTH, (Runnable r) -> r.run(), @@ -59,6 +76,22 @@ public class BluetoothDeviceConfigListener { } Log.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); } + + boolean apmEnhancement = properties.getBoolean( + APM_ENHANCEMENT, mPrevApmEnhancement); + if (apmEnhancement != mPrevApmEnhancement) { + mPrevApmEnhancement = apmEnhancement; + Settings.Global.putInt(mContext.getContentResolver(), + APM_ENHANCEMENT, apmEnhancement ? 1 : 0); + } + + boolean btApmState = properties.getBoolean( + BT_DEFAULT_APM_STATE, mPrevBtApmState); + if (btApmState != mPrevBtApmState) { + mPrevBtApmState = btApmState; + Settings.Global.putInt(mContext.getContentResolver(), + BT_DEFAULT_APM_STATE, btApmState ? 1 : 0); + } boolean foundInit = false; for (String name : properties.getKeyset()) { if (name.startsWith("INIT_")) { @@ -72,5 +105,4 @@ public class BluetoothDeviceConfigListener { mService.onInitFlagsChanged(); } }; - } diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java index d3aec713bba..fb815d478e3 100644 --- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java +++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java @@ -21,6 +21,13 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGRO import static android.permission.PermissionManager.PERMISSION_GRANTED; import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_BT_ENABLED_NOTIFICATION; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_USER_TOGGLED_BLUETOOTH; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.NOTIFICATION_NOT_SHOWN; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.USED; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -182,6 +189,9 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { @VisibleForTesting static final int BLUETOOTH_ON_AIRPLANE = 2; + private static final int BLUETOOTH_OFF_APM = 0; + private static final int BLUETOOTH_ON_APM = 1; + private static final int SERVICE_IBLUETOOTH = 1; private static final int SERVICE_IBLUETOOTHGATT = 2; @@ -227,6 +237,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener; + private BluetoothNotificationManager mBluetoothNotificationManager; + // used inside handler thread private boolean mQuietEnable = false; private boolean mEnable; @@ -542,6 +554,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mUserManager = mContext.getSystemService(UserManager.class); + mBluetoothNotificationManager = new BluetoothNotificationManager(mContext); + mIsHearingAidProfileSupported = BluetoothProperties.isProfileAshaCentralEnabled().orElse(false); @@ -602,7 +616,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { if (airplaneModeRadios == null || airplaneModeRadios.contains( Settings.Global.RADIO_BLUETOOTH)) { mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - this, mBluetoothHandlerThread.getLooper(), context); + this, mBluetoothHandlerThread.getLooper(), context, + mBluetoothNotificationManager); } int systemUiUid = -1; @@ -627,6 +642,13 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } + /** + * Returns true if airplane mode enhancement feature is enabled + */ + private boolean isApmEnhancementOn() { + return Settings.Global.getInt(mContext.getContentResolver(), APM_ENHANCEMENT, 0) == 1; + } + private boolean supportBluetoothPersistedState() { // Set default support to true to copy config default. return BluetoothProperties.isSupportPersistedStateEnabled().orElse(true); @@ -685,6 +707,43 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } } + /** + * Set the Settings Secure Int value for foreground user + */ + private void setSettingsSecureInt(String name, int value) { + if (DBG) { + Log.d(TAG, "Persisting Settings Secure Int: " + name + "=" + value); + } + + // waive WRITE_SECURE_SETTINGS permission check + final long callingIdentity = Binder.clearCallingIdentity(); + try { + Context userContext = mContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); + Settings.Secure.putInt(userContext.getContentResolver(), name, value); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * Return whether APM notification has been shown + */ + private boolean isFirstTimeNotification(String name) { + boolean firstTime = false; + // waive WRITE_SECURE_SETTINGS permission check + final long callingIdentity = Binder.clearCallingIdentity(); + try { + Context userContext = mContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); + firstTime = Settings.Secure.getInt(userContext.getContentResolver(), name, + NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return firstTime; + } + /** * Returns true if the Bluetooth Adapter's name and address is * locally cached @@ -1321,6 +1380,23 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { synchronized (mReceiver) { mQuietEnableExternal = false; mEnableExternal = true; + if (isAirplaneModeOn() && isApmEnhancementOn()) { + setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_ON_APM); + setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); + if (isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mBluetoothAirplaneModeListener.sendApmNotification( + "bluetooth_enabled_apm_title", + "bluetooth_enabled_apm_message", + APM_BT_ENABLED_NOTIFICATION); + } catch (Exception e) { + Log.e(TAG, "APM enhancement BT enabled notification not shown"); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + } // waive WRITE_SECURE_SETTINGS permission check sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); @@ -1361,12 +1437,15 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } synchronized (mReceiver) { - if (!isBluetoothPersistedStateOnAirplane()) { - if (persist) { - persistBluetoothSetting(BLUETOOTH_OFF); - } - mEnableExternal = false; + if (isAirplaneModeOn() && isApmEnhancementOn()) { + setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_OFF_APM); + setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); + } + + if (persist) { + persistBluetoothSetting(BLUETOOTH_OFF); } + mEnableExternal = false; sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } @@ -1571,7 +1650,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); } registerForProvisioningStateChange(); - mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); + mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG, mContext); } /** @@ -2428,6 +2507,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Log.d(TAG, "MESSAGE_USER_SWITCHED"); } mHandler.removeMessages(MESSAGE_USER_SWITCHED); + mBluetoothNotificationManager.createNotificationChannels(); /* disable and enable BT when detect a user switch */ if (mBluetooth != null && isEnabled()) { diff --git a/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java b/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java index 4d3f22e5fed..359b5d8320c 100644 --- a/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java +++ b/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java @@ -16,7 +16,11 @@ package com.android.server.bluetooth; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; + import android.annotation.RequiresPermission; +import android.app.ActivityManager; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHearingAid; @@ -24,8 +28,12 @@ import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; +import android.os.Process; +import android.os.UserHandle; import android.provider.Settings; +import android.util.Log; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; @@ -35,12 +43,16 @@ import com.android.internal.annotations.VisibleForTesting; * complex logic. */ public class BluetoothModeChangeHelper { + private static final String TAG = "BluetoothModeChangeHelper"; + private volatile BluetoothA2dp mA2dp; private volatile BluetoothHearingAid mHearingAid; private volatile BluetoothLeAudio mLeAudio; private final BluetoothAdapter mAdapter; private final Context mContext; + private String mBluetoothPackageName; + BluetoothModeChangeHelper(Context context) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mContext = context; @@ -127,6 +139,24 @@ public class BluetoothModeChangeHelper { name, value); } + /** + * Helper method to get Settings Secure Int value + */ + public int getSettingsSecureInt(String name, int def) { + Context userContext = mContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); + return Settings.Secure.getInt(userContext.getContentResolver(), name, def); + } + + /** + * Helper method to set Settings Secure Int value + */ + public void setSettingsSecureInt(String name, int value) { + Context userContext = mContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); + Settings.Secure.putInt(userContext.getContentResolver(), name, value); + } + @VisibleForTesting public void showToastMessage() { Resources r = mContext.getResources(); @@ -158,4 +188,45 @@ public class BluetoothModeChangeHelper { } return leAudio.getConnectedDevices().size() > 0; } + + /** + * Helper method to check whether BT should be enabled on APM + */ + public boolean isBluetoothOnAPM() { + Context userContext = mContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); + int defaultBtApmState = getSettingsInt(BT_DEFAULT_APM_STATE); + return Settings.Secure.getInt(userContext.getContentResolver(), + BLUETOOTH_APM_STATE, defaultBtApmState) == 1; + } + + /** + * Helper method to retrieve BT package name with APM resources + */ + public String getBluetoothPackageName() { + if (mBluetoothPackageName != null) { + return mBluetoothPackageName; + } + var allPackages = mContext.getPackageManager().getPackagesForUid(Process.BLUETOOTH_UID); + for (String candidatePackage : allPackages) { + Resources resources; + try { + resources = mContext.getPackageManager() + .getResourcesForApplication(candidatePackage); + } catch (PackageManager.NameNotFoundException e) { + // ignore, try next package + Log.e(TAG, "Could not find package " + candidatePackage); + continue; + } catch (Exception e) { + Log.e(TAG, "Error while loading package" + e); + continue; + } + if (resources.getIdentifier("bluetooth_and_wifi_stays_on_title", + "string", candidatePackage) == 0) { + continue; + } + mBluetoothPackageName = candidatePackage; + } + return mBluetoothPackageName; + } } diff --git a/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java b/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java new file mode 100644 index 00000000000..d2abde092b7 --- /dev/null +++ b/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java @@ -0,0 +1,162 @@ +/* + * 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.server.bluetooth; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Notification manager for Bluetooth. All notification will be sent to the current user. + */ +public class BluetoothNotificationManager { + private static final String TAG = "BluetoothNotificationManager"; + private static final String NOTIFICATION_TAG = "com.android.bluetooth"; + public static final String APM_NOTIFICATION_CHANNEL = "apm_notification_channel"; + private static final String APM_NOTIFICATION_GROUP = "apm_notification_group"; + + //TODO: b/239983569 remove hardcoded notification ID + private static final int NOTE_BT_APM_NOTIFICATION = -1000007; + + private final Context mContext; + private NotificationManager mNotificationManager; + + private boolean mInitialized = false; + + /** + * Constructor + * + * @param ctx The context to use to obtain access to the Notification Service + */ + BluetoothNotificationManager(Context ctx) { + mContext = ctx; + } + + private NotificationManager getNotificationManagerForCurrentUser() { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, + UserHandle.CURRENT).getSystemService(NotificationManager.class); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get NotificationManager for current user: " + e.getMessage()); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return null; + } + + /** + * Update to the notification manager fot current user and create notification channels. + */ + public void createNotificationChannels() { + if (mNotificationManager != null) { + // Cancel all active notification from Bluetooth Stack. + cleanAllBtNotification(); + } + mNotificationManager = getNotificationManagerForCurrentUser(); + if (mNotificationManager == null) { + return; + } + List channelsList = new ArrayList<>(); + + final NotificationChannel apmChannel = new NotificationChannel( + APM_NOTIFICATION_CHANNEL, + APM_NOTIFICATION_GROUP, + NotificationManager.IMPORTANCE_HIGH); + channelsList.add(apmChannel); + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mNotificationManager.createNotificationChannels(channelsList); + } catch (Exception e) { + Log.e(TAG, "Error Message: " + e.getMessage()); + e.printStackTrace(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private void cleanAllBtNotification() { + for (StatusBarNotification notification : getActiveNotifications()) { + if (NOTIFICATION_TAG.equals(notification.getTag())) { + cancel(notification.getId()); + } + } + } + + /** + * Send notification to the current user. + */ + public void notify(int id, Notification notification) { + if (!mInitialized) { + createNotificationChannels(); + mInitialized = true; + } + if (mNotificationManager == null) { + return; + } + mNotificationManager.notify(NOTIFICATION_TAG, id, notification); + } + + /** + * Build and send the APM notification. + */ + public void sendApmNotification(String title, String message) { + if (!mInitialized) { + createNotificationChannels(); + mInitialized = true; + } + Notification notification = new Notification.Builder(mContext, APM_NOTIFICATION_CHANNEL) + .setAutoCancel(true) + .setLocalOnly(true) + .setContentTitle(title) + .setContentText(message) + .setStyle(new Notification.BigTextStyle().bigText(message)) + .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) + .build(); + notify(NOTE_BT_APM_NOTIFICATION, notification); + } + + /** + * Cancel the notification fot current user. + */ + public void cancel(int id) { + if (mNotificationManager == null) { + return; + } + mNotificationManager.cancel(NOTIFICATION_TAG, id); + } + + /** + * Get active notifications for current user. + */ + public StatusBarNotification[] getActiveNotifications() { + if (mNotificationManager == null) { + return new StatusBarNotification[0]; + } + return mNotificationManager.getActiveNotifications(); + } +} diff --git a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java index fb067800a10..8c7ec8ec7f6 100644 --- a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java +++ b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java @@ -40,6 +40,7 @@ public class BluetoothAirplaneModeListenerTest { private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; private BluetoothAdapter mBluetoothAdapter; private BluetoothModeChangeHelper mHelper; + private BluetoothNotificationManager mBluetoothNotificationManager; @Mock BluetoothManagerService mBluetoothManagerService; @@ -55,7 +56,8 @@ public class BluetoothAirplaneModeListenerTest { doNothing().when(mHelper).onAirplaneModeChanged(any(BluetoothManagerService.class)); mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - mBluetoothManagerService, Looper.getMainLooper(), mContext); + mBluetoothManagerService, Looper.getMainLooper(), mContext, + mBluetoothNotificationManager); mBluetoothAirplaneModeListener.start(mHelper); } -- GitLab From 730cb6c65054a20d18deae761450acc4333f054b Mon Sep 17 00:00:00 2001 From: Rahul Arya Date: Fri, 29 Jul 2022 16:46:38 +0000 Subject: [PATCH 013/998] Fix CDMA conference active call inference The special logic here is only needed for CDMA calls, since they don't let us see what the active call is directly. But the rest of the logic should be used for all conference calls. So move the CDMA-specific check to gate just the relevant part. Bug: 234629992 Test: manual + BluetoothInCallServiceTest Tag: #stability Change-Id: I43e50d5e672ec0cb9ab41445f79d60b9e5a567cd (cherry picked from commit 272c46476e872e9e02bc018b689a2a1fe0d160b9) Merged-In: I43e50d5e672ec0cb9ab41445f79d60b9e5a567cd --- .../telephony/BluetoothInCallService.java | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java index 85ebcabeef9..dbbb95b70e7 100644 --- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java +++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java @@ -713,37 +713,39 @@ public class BluetoothInCallService extends InCallService { } BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); - if (!mCallInfo.isNullCall(conferenceCall) - && conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)) { + if (!mCallInfo.isNullCall(conferenceCall)) { isPartOfConference = true; - // Run some alternative states for Conference-level merge/swap support. - // Basically, if BluetoothCall supports swapping or merging at the conference-level, - // then we need to expose the calls as having distinct states - // (ACTIVE vs CAPABILITY_HOLD) or - // the functionality won't show up on the bluetooth device. - - // Before doing any special logic, ensure that we are dealing with an - // ACTIVE BluetoothCall and that the conference itself has a notion of - // the current "active" child call. - BluetoothCall activeChild = getBluetoothCallById( - conferenceCall.getGenericConferenceActiveChildCallId()); - if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) { - // Reevaluate state if we can MERGE or if we can SWAP without previously having - // MERGED. - boolean shouldReevaluateState = - conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) - || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) - && !conferenceCall.wasConferencePreviouslyMerged()); + if (conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)) { + // Run some alternative states for CDMA Conference-level merge/swap support. + // Basically, if BluetoothCall supports swapping or merging at the conference-level, + // then we need to expose the calls as having distinct states + // (ACTIVE vs CAPABILITY_HOLD) or + // the functionality won't show up on the bluetooth device. + + // Before doing any special logic, ensure that we are dealing with an + // ACTIVE BluetoothCall and that the conference itself has a notion of + // the current "active" child call. + BluetoothCall activeChild = + getBluetoothCallById( + conferenceCall.getGenericConferenceActiveChildCallId()); + if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) { + // Reevaluate state if we can MERGE or if we can SWAP without previously having + // MERGED. + boolean shouldReevaluateState = + conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) + || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) + && !conferenceCall.wasConferencePreviouslyMerged()); - if (shouldReevaluateState) { - isPartOfConference = false; - if (call == activeChild) { - state = CALL_STATE_ACTIVE; - } else { - // At this point we know there is an "active" child and we know that it is - // not this call, so set it to HELD instead. - state = CALL_STATE_HELD; + if (shouldReevaluateState) { + isPartOfConference = false; + if (call == activeChild) { + state = CALL_STATE_ACTIVE; + } else { + // At this point we know there is an "active" child and we know that it + // is not this call, so set it to HELD instead. + state = CALL_STATE_HELD; + } } } } -- GitLab From ca4a03aa6d81bc32984ce794cab7e9bc1fed539d Mon Sep 17 00:00:00 2001 From: kuanyuhuang Date: Wed, 3 Aug 2022 11:27:38 +0000 Subject: [PATCH 014/998] Support setting BT controller Low Latency mode(1/2) Handle audio api StartRequest and SetLatencyMode in a2dp to send stream latency through btif_av to bta event. Bug: 240637363 Bug: 223126227 Tag: #feature Ignore-AOSP-First: TM QPR1 feature Test: test on sink device support dynamic spatial audio Change-Id: I9ad4a23a58eeca6128a9f04a530ae6c90c773da5 --- .../aidl/a2dp_encoding_aidl.cc | 8 +- .../audio_hal_interface/aidl/a2dp_transport.h | 4 +- .../aidl/bluetooth_audio_port_impl.cc | 3 +- .../hearing_aid_software_encoding_aidl.cc | 4 +- .../aidl/le_audio_software_aidl.cc | 12 ++- .../aidl/le_audio_software_aidl.h | 8 +- .../aidl/transport_instance.h | 4 +- system/bta/av/bta_av_aact.cc | 3 + system/bta/av/bta_av_act.cc | 13 +++ system/bta/av/bta_av_api.cc | 41 +++++++-- system/bta/av/bta_av_int.h | 19 +++- system/bta/include/bta_av_api.h | 13 ++- system/btif/include/btif_av.h | 12 +++ system/btif/src/btif_av.cc | 90 ++++++++++++++++++- system/stack/include/l2c_api.h | 12 +++ system/stack/l2cap/l2c_api.cc | 23 +++++ system/stack/l2cap/l2c_int.h | 2 + system/test/mock/mock_bta_av_api.cc | 7 +- system/test/mock/mock_stack_l2cap_api.cc | 6 ++ system/test/mock/mock_stack_l2cap_api.h | 11 +++ 20 files changed, 273 insertions(+), 22 deletions(-) diff --git a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc index faa735a2e00..1ea3b3c1594 100644 --- a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc +++ b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc @@ -90,7 +90,7 @@ BluetoothAudioCtrlAck A2dpTransport::StartRequest(bool is_low_latency) { * procedure is completed, othewise send it now. */ a2dp_pending_cmd_ = A2DP_CTRL_CMD_START; - btif_av_stream_start(); + btif_av_stream_start_with_latency(is_low_latency); if (btif_av_get_peer_sep() != AVDT_TSEP_SRC) { LOG(INFO) << __func__ << ": accepted"; return a2dp_ack_to_bt_audio_ctrl_ack(A2DP_CTRL_ACK_PENDING); @@ -138,6 +138,10 @@ void A2dpTransport::StopRequest() { btif_av_stream_stop(RawAddress::kEmpty); } +void A2dpTransport::SetLowLatency(bool is_low_latency) { + btif_av_set_low_latency(is_low_latency); +} + bool A2dpTransport::GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_read, timespec* data_position) { @@ -569,4 +573,4 @@ void set_low_latency_mode_allowed(bool allowed) { } // namespace a2dp } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/a2dp_transport.h b/system/audio_hal_interface/aidl/a2dp_transport.h index 5732754ca1e..1b53da0c419 100644 --- a/system/audio_hal_interface/aidl/a2dp_transport.h +++ b/system/audio_hal_interface/aidl/a2dp_transport.h @@ -39,6 +39,8 @@ class A2dpTransport void StopRequest() override; + void SetLowLatency(bool is_low_latency) override; + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_read, timespec* data_position) override; @@ -69,4 +71,4 @@ class A2dpTransport } // namespace a2dp } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc b/system/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc index d9ce929bbec..813648acb5a 100644 --- a/system/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc +++ b/system/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc @@ -137,6 +137,7 @@ ndk::ScopedAStatus BluetoothAudioPortImpl::setLatencyMode( LatencyMode latency_mode) { bool is_low_latency = latency_mode == LatencyMode::LOW_LATENCY ? true : false; invoke_switch_buffer_size_cb(is_low_latency); + transport_instance_->SetLowLatency(is_low_latency); return ndk::ScopedAStatus::ok(); } @@ -148,4 +149,4 @@ PresentationPosition::TimeSpec BluetoothAudioPortImpl::timespec_convert_to_hal( } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/hearing_aid_software_encoding_aidl.cc b/system/audio_hal_interface/aidl/hearing_aid_software_encoding_aidl.cc index 91b4dcac020..1cab476250f 100644 --- a/system/audio_hal_interface/aidl/hearing_aid_software_encoding_aidl.cc +++ b/system/audio_hal_interface/aidl/hearing_aid_software_encoding_aidl.cc @@ -74,6 +74,8 @@ class HearingAidTransport } } + void SetLowLatency(bool is_low_latency) override {} + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_read, timespec* data_position) override { @@ -267,4 +269,4 @@ void set_remote_delay(uint16_t delay_report_ms) { } // namespace hearing_aid } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/le_audio_software_aidl.cc b/system/audio_hal_interface/aidl/le_audio_software_aidl.cc index 80aed9e8584..ffc213f075a 100644 --- a/system/audio_hal_interface/aidl/le_audio_software_aidl.cc +++ b/system/audio_hal_interface/aidl/le_audio_software_aidl.cc @@ -93,6 +93,8 @@ void LeAudioTransport::StopRequest() { } } +void LeAudioTransport::SetLowLatency(bool is_low_latency) {} + bool LeAudioTransport::GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_processed, timespec* data_position) { @@ -244,6 +246,10 @@ BluetoothAudioCtrlAck LeAudioSinkTransport::SuspendRequest() { void LeAudioSinkTransport::StopRequest() { transport_->StopRequest(); } +void LeAudioSinkTransport::SetLowLatency(bool is_low_latency) { + transport_->SetLowLatency(is_low_latency); +} + bool LeAudioSinkTransport::GetPresentationPosition( uint64_t* remote_delay_report_ns, uint64_t* total_bytes_read, timespec* data_position) { @@ -327,6 +333,10 @@ BluetoothAudioCtrlAck LeAudioSourceTransport::SuspendRequest() { void LeAudioSourceTransport::StopRequest() { transport_->StopRequest(); } +void LeAudioSourceTransport::SetLowLatency(bool is_low_latency) { + transport_->SetLowLatency(is_low_latency); +} + bool LeAudioSourceTransport::GetPresentationPosition( uint64_t* remote_delay_report_ns, uint64_t* total_bytes_written, timespec* data_position) { @@ -538,4 +548,4 @@ AudioConfiguration offload_config_to_hal_audio_config( } // namespace le_audio } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/le_audio_software_aidl.h b/system/audio_hal_interface/aidl/le_audio_software_aidl.h index 2958d262c98..1b54c840784 100644 --- a/system/audio_hal_interface/aidl/le_audio_software_aidl.h +++ b/system/audio_hal_interface/aidl/le_audio_software_aidl.h @@ -76,6 +76,8 @@ class LeAudioTransport { void StopRequest(); + void SetLowLatency(bool is_low_latency); + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_processed, timespec* data_position); @@ -129,6 +131,8 @@ class LeAudioSinkTransport void StopRequest() override; + void SetLowLatency(bool is_low_latency) override; + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_read, timespec* data_position) override; @@ -181,6 +185,8 @@ class LeAudioSourceTransport void StopRequest() override; + void SetLowLatency(bool is_low_latency) override; + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_written, timespec* data_position) override; @@ -214,4 +220,4 @@ class LeAudioSourceTransport } // namespace le_audio } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/transport_instance.h b/system/audio_hal_interface/aidl/transport_instance.h index 11b9a21a05b..e7967ab2c4a 100644 --- a/system/audio_hal_interface/aidl/transport_instance.h +++ b/system/audio_hal_interface/aidl/transport_instance.h @@ -71,6 +71,8 @@ class IBluetoothTransportInstance { virtual void StopRequest() = 0; + virtual void SetLowLatency(bool is_low_latency) = 0; + virtual bool GetPresentationPosition(uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed, timespec* data_position) = 0; @@ -122,4 +124,4 @@ class IBluetoothSourceTransportInstance : public IBluetoothTransportInstance { } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/bta/av/bta_av_aact.cc b/system/bta/av/bta_av_aact.cc index 9e48782172b..92a8af7cd00 100644 --- a/system/bta/av/bta_av_aact.cc +++ b/system/bta/av/bta_av_aact.cc @@ -1824,6 +1824,7 @@ void bta_av_do_start(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) { if (p_scb->role & BTA_AV_ROLE_SUSPEND) { notify_start_failed(p_scb); } else { + bta_av_set_use_latency_mode(p_scb, p_data->do_start.use_latency_mode); bta_av_start_ok(p_scb, NULL); } return; @@ -1858,6 +1859,8 @@ void bta_av_do_start(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) { LOG_ERROR("%s: AVDT_StartReq failed for peer %s result:%d", __func__, p_scb->PeerAddress().ToString().c_str(), result); bta_av_start_failed(p_scb, p_data); + } else { + bta_av_set_use_latency_mode(p_scb, p_data->do_start.use_latency_mode); } LOG_INFO( "%s: peer %s start requested: sco_occupied:%s role:0x%x " diff --git a/system/bta/av/bta_av_act.cc b/system/bta/av/bta_av_act.cc index 177377c0213..9d3c90d9cf1 100644 --- a/system/bta/av/bta_av_act.cc +++ b/system/bta/av/bta_av_act.cc @@ -1339,6 +1339,19 @@ void bta_av_api_disconnect(tBTA_AV_DATA* p_data) { alarm_cancel(p_scb->link_signalling_timer); } +/******************************************************************************* + * + * Function bta_av_set_use_latency_mode + * + * Description Sets stream use latency mode. + * + * Returns void + * + ******************************************************************************/ +void bta_av_set_use_latency_mode(tBTA_AV_SCB* p_scb, bool use_latency_mode) { + L2CA_UseLatencyMode(p_scb->PeerAddress(), use_latency_mode); +} + /** * Find the index for the free LCB entry to use. * diff --git a/system/bta/av/bta_av_api.cc b/system/bta/av/bta_av_api.cc index 9a8ccfd688b..defaea19de3 100644 --- a/system/bta/av/bta_av_api.cc +++ b/system/bta/av/bta_av_api.cc @@ -215,13 +215,17 @@ void BTA_AvDisconnect(tBTA_AV_HNDL handle) { * Returns void * ******************************************************************************/ -void BTA_AvStart(tBTA_AV_HNDL handle) { - LOG_INFO("Starting audio/video stream data transfer bta_handle:%hhu", handle); - - BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID)); - - p_buf->event = BTA_AV_API_START_EVT; - p_buf->layer_specific = handle; +void BTA_AvStart(tBTA_AV_HNDL handle, bool use_latency_mode) { + LOG_INFO( + "Starting audio/video stream data transfer bta_handle:%hhu, " + "use_latency_mode:%s", + handle, use_latency_mode ? "true" : "false"); + + tBTA_AV_DO_START* p_buf = + (tBTA_AV_DO_START*)osi_malloc(sizeof(tBTA_AV_DO_START)); + p_buf->hdr.event = BTA_AV_API_START_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->use_latency_mode = use_latency_mode; bta_sys_sendmsg(p_buf); } @@ -613,3 +617,26 @@ void BTA_AvMetaCmd(uint8_t rc_handle, uint8_t label, tBTA_AV_CMD cmd_code, bta_sys_sendmsg(p_buf); } + +/******************************************************************************* + * + * Function BTA_AvSetLatency + * + * Description Set audio/video stream latency. + * + * Returns void + * + ******************************************************************************/ +void BTA_AvSetLatency(tBTA_AV_HNDL handle, bool is_low_latency) { + LOG_INFO( + "Set audio/video stream low latency bta_handle:%hhu, is_low_latency:%s", + handle, is_low_latency ? "true" : "false"); + + tBTA_AV_API_SET_LATENCY* p_buf = + (tBTA_AV_API_SET_LATENCY*)osi_malloc(sizeof(tBTA_AV_API_SET_LATENCY)); + p_buf->hdr.event = BTA_AV_API_SET_LATENCY_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->is_low_latency = is_low_latency; + + bta_sys_sendmsg(p_buf); +} diff --git a/system/bta/av/bta_av_int.h b/system/bta/av/bta_av_int.h index 0b3e914b1d3..49b3ce432b8 100644 --- a/system/bta/av/bta_av_int.h +++ b/system/bta/av/bta_av_int.h @@ -114,7 +114,8 @@ enum { BTA_AV_AVDT_RPT_CONN_EVT, BTA_AV_API_START_EVT, /* the following 2 events must be in the same order as the *AP_*EVT */ - BTA_AV_API_STOP_EVT + BTA_AV_API_STOP_EVT, + BTA_AV_API_SET_LATENCY_EVT, }; /* events for AV control block state machine */ @@ -264,6 +265,18 @@ typedef struct { uint16_t uuid; /* uuid of initiator */ } tBTA_AV_API_OPEN; +/* data type for BTA_AV_API_SET_LATENCY_EVT */ +typedef struct { + BT_HDR_RIGID hdr; + bool is_low_latency; +} tBTA_AV_API_SET_LATENCY; + +/* data type for BTA_AV_API_START_EVT and bta_av_do_start */ +typedef struct { + BT_HDR_RIGID hdr; + bool use_latency_mode; +} tBTA_AV_DO_START; + /* data type for BTA_AV_API_STOP_EVT */ typedef struct { BT_HDR_RIGID hdr; @@ -429,6 +442,8 @@ union tBTA_AV_DATA { tBTA_AV_API_ENABLE api_enable; tBTA_AV_API_REG api_reg; tBTA_AV_API_OPEN api_open; + tBTA_AV_API_SET_LATENCY api_set_latency; + tBTA_AV_DO_START do_start; tBTA_AV_API_STOP api_stop; tBTA_AV_API_DISCNT api_discnt; tBTA_AV_API_PROTECT_REQ api_protect_req; @@ -724,6 +739,8 @@ extern bool bta_av_link_role_ok(tBTA_AV_SCB* p_scb, uint8_t bits); /* nsm action functions */ extern void bta_av_api_disconnect(tBTA_AV_DATA* p_data); +extern void bta_av_set_use_latency_mode(tBTA_AV_SCB* p_scb, + bool use_latency_mode); extern void bta_av_sig_chg(tBTA_AV_DATA* p_data); extern void bta_av_signalling_timer(tBTA_AV_DATA* p_data); extern void bta_av_rc_disc_done(tBTA_AV_DATA* p_data); diff --git a/system/bta/include/bta_av_api.h b/system/bta/include/bta_av_api.h index fd1dd000df9..9112a32ad76 100644 --- a/system/bta/include/bta_av_api.h +++ b/system/bta/include/bta_av_api.h @@ -500,7 +500,7 @@ void BTA_AvDisconnect(tBTA_AV_HNDL handle); * Returns void * ******************************************************************************/ -void BTA_AvStart(tBTA_AV_HNDL handle); +void BTA_AvStart(tBTA_AV_HNDL handle, bool use_latency_mode); /******************************************************************************* * @@ -674,6 +674,17 @@ void BTA_AvMetaRsp(uint8_t rc_handle, uint8_t label, tBTA_AV_CODE rsp_code, void BTA_AvMetaCmd(uint8_t rc_handle, uint8_t label, tBTA_AV_CMD cmd_code, BT_HDR* p_pkt); +/******************************************************************************* + * + * Function BTA_AvSetLatency + * + * Description Set audio/video stream latency. + * + * Returns void + * + ******************************************************************************/ +void BTA_AvSetLatency(tBTA_AV_HNDL handle, bool is_low_latency); + /******************************************************************************* * * Function BTA_AvOffloadStart diff --git a/system/btif/include/btif_av.h b/system/btif/include/btif_av.h index 0a3095ee570..09c0e2721c2 100644 --- a/system/btif/include/btif_av.h +++ b/system/btif/include/btif_av.h @@ -51,6 +51,11 @@ bool btif_av_is_sink_enabled(void); */ void btif_av_stream_start(void); +/** + * Start streaming with latency setting. + */ +void btif_av_stream_start_with_latency(bool use_latency_mode); + /** * Stop streaming. * @@ -228,4 +233,11 @@ bool btif_av_is_peer_silenced(const RawAddress& peer_address); */ void btif_av_set_dynamic_audio_buffer_size(uint8_t dynamic_audio_buffer_size); +/** + * Enable/disable the low latency + * + * @param is_low_latency to set + */ +void btif_av_set_low_latency(bool is_low_latency); + #endif /* BTIF_AV_H */ diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc index 8ae8db8add7..7f613b87adc 100644 --- a/system/btif/src/btif_av.cc +++ b/system/btif/src/btif_av.cc @@ -80,6 +80,14 @@ typedef struct { RawAddress peer_address; } btif_av_sink_config_req_t; +typedef struct { + bool use_latency_mode; +} btif_av_start_stream_req_t; + +typedef struct { + bool is_low_latency; +} btif_av_set_latency_req_t; + /** * BTIF AV events */ @@ -96,6 +104,7 @@ typedef enum { BTIF_AV_AVRCP_OPEN_EVT, BTIF_AV_AVRCP_CLOSE_EVT, BTIF_AV_AVRCP_REMOTE_PLAY_EVT, + BTIF_AV_SET_LATENCY_REQ_EVT, } btif_av_sm_event_t; class BtifAvEvent { @@ -340,6 +349,11 @@ class BtifAvPeer { bool SelfInitiatedConnection() const { return self_initiated_connection_; } void SetSelfInitiatedConnection(bool v) { self_initiated_connection_ = v; } + bool UseLatencyMode() const { return use_latency_mode_; } + void SetUseLatencyMode(bool use_latency_mode) { + use_latency_mode_ = use_latency_mode; + } + private: const RawAddress peer_address_; const uint8_t peer_sep_; // SEP type of peer device @@ -353,6 +367,7 @@ class BtifAvPeer { bool is_silenced_; uint16_t delay_report_; bool mandatory_codec_preferred_ = false; + bool use_latency_mode_ = false; }; class BtifAvSource { @@ -769,6 +784,7 @@ const char* dump_av_sm_event_name(btif_av_sm_event_t event) { CASE_RETURN_STR(BTIF_AV_AVRCP_OPEN_EVT) CASE_RETURN_STR(BTIF_AV_AVRCP_CLOSE_EVT) CASE_RETURN_STR(BTIF_AV_AVRCP_REMOTE_PLAY_EVT) + CASE_RETURN_STR(BTIF_AV_SET_LATENCY_REQ_EVT) default: return "UNKNOWN_EVENT"; } @@ -1908,14 +1924,22 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event, case BTIF_AV_ACL_DISCONNECTED: break; // Ignore - case BTIF_AV_START_STREAM_REQ_EVT: + case BTIF_AV_START_STREAM_REQ_EVT: { LOG_INFO("%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), BtifAvEvent::EventName(event).c_str(), peer_.FlagsToString().c_str()); - BTA_AvStart(peer_.BtaHandle()); + if (p_data) { + const btif_av_start_stream_req_t* p_start_steam_req = + static_cast(p_data); + LOG_INFO("Stream use_latency_mode=%s", + p_start_steam_req->use_latency_mode ? "true" : "false"); + peer_.SetUseLatencyMode(p_start_steam_req->use_latency_mode); + } + + BTA_AvStart(peer_.BtaHandle(), peer_.UseLatencyMode()); peer_.SetFlags(BtifAvPeer::kFlagPendingStart); - break; + } break; case BTA_AV_START_EVT: { LOG_INFO( @@ -2039,7 +2063,7 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event, LOG(INFO) << __PRETTY_FUNCTION__ << " : Peer " << peer_.PeerAddress() << " : Reconfig done - calling BTA_AvStart(" << loghex(peer_.BtaHandle()) << ")"; - BTA_AvStart(peer_.BtaHandle()); + BTA_AvStart(peer_.BtaHandle(), peer_.UseLatencyMode()); } break; @@ -2070,6 +2094,18 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event, CHECK_RC_EVENT(event, (tBTA_AV*)p_data); + case BTIF_AV_SET_LATENCY_REQ_EVT: { + const btif_av_set_latency_req_t* p_set_latency_req = + static_cast(p_data); + LOG_INFO("Peer %s : event=%s flags=%s is_low_latency=%s", + peer_.PeerAddress().ToString().c_str(), + BtifAvEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + p_set_latency_req->is_low_latency ? "true" : "false"); + + BTA_AvSetLatency(peer_.BtaHandle(), p_set_latency_req->is_low_latency); + } break; + default: BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", __PRETTY_FUNCTION__, @@ -2273,6 +2309,18 @@ bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event, btif_a2dp_on_offload_started(peer_.PeerAddress(), p_av->status); break; + case BTIF_AV_SET_LATENCY_REQ_EVT: { + const btif_av_set_latency_req_t* p_set_latency_req = + static_cast(p_data); + LOG_INFO("Peer %s : event=%s flags=%s is_low_latency=%s", + peer_.PeerAddress().ToString().c_str(), + BtifAvEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + p_set_latency_req->is_low_latency ? "true" : "false"); + + BTA_AvSetLatency(peer_.BtaHandle(), p_set_latency_req->is_low_latency); + } break; + CHECK_RC_EVENT(event, (tBTA_AV*)p_data); default: @@ -3117,6 +3165,24 @@ void btif_av_stream_start(void) { BTIF_AV_START_STREAM_REQ_EVT); } +void btif_av_stream_start_with_latency(bool use_latency_mode) { + LOG_INFO("%s", __func__); + + btif_av_start_stream_req_t start_stream_req; + start_stream_req.use_latency_mode = use_latency_mode; + BtifAvEvent btif_av_event(BTIF_AV_START_STREAM_REQ_EVT, &start_stream_req, + sizeof(start_stream_req)); + LOG_INFO("peer_address=%s event=%s use_latency_mode=%s", + btif_av_source_active_peer().ToString().c_str(), + btif_av_event.ToString().c_str(), + use_latency_mode ? "true" : "false"); + + do_in_main_thread(FROM_HERE, base::Bind(&btif_av_handle_event, + AVDT_TSEP_SNK, // peer_sep + btif_av_source_active_peer(), + kBtaHandleUnknown, btif_av_event)); +} + void src_do_suspend_in_main_thread(btif_av_sm_event_t event) { if (event != BTIF_AV_SUSPEND_STREAM_REQ_EVT && event != BTIF_AV_STOP_STREAM_REQ_EVT) @@ -3534,3 +3600,19 @@ bool btif_av_is_peer_silenced(const RawAddress& peer_address) { void btif_av_set_dynamic_audio_buffer_size(uint8_t dynamic_audio_buffer_size) { btif_a2dp_source_set_dynamic_audio_buffer_size(dynamic_audio_buffer_size); } + +void btif_av_set_low_latency(bool is_low_latency) { + LOG_INFO("is_low_latency: %s", is_low_latency ? "true" : "false"); + + btif_av_set_latency_req_t set_latency_req; + set_latency_req.is_low_latency = is_low_latency; + BtifAvEvent btif_av_event(BTIF_AV_SET_LATENCY_REQ_EVT, &set_latency_req, + sizeof(set_latency_req)); + LOG_INFO("peer_address=%s event=%s", + btif_av_source_active_peer().ToString().c_str(), + btif_av_event.ToString().c_str()); + do_in_main_thread(FROM_HERE, base::Bind(&btif_av_handle_event, + AVDT_TSEP_SNK, // peer_sep + btif_av_source_active_peer(), + kBtaHandleUnknown, btif_av_event)); +} diff --git a/system/stack/include/l2c_api.h b/system/stack/include/l2c_api.h index 92cf17ff396..1a2bc407b09 100644 --- a/system/stack/include/l2c_api.h +++ b/system/stack/include/l2c_api.h @@ -634,6 +634,18 @@ extern uint8_t L2CA_SetTraceLevel(uint8_t trace_level); ******************************************************************************/ extern uint16_t L2CA_FlushChannel(uint16_t lcid, uint16_t num_to_flush); +/******************************************************************************* + * + * Function L2CA_UseLatencyMode + * + * Description Sets use latency mode for an ACL channel. + * + * Returns true if a valid channel, else false + * + ******************************************************************************/ +extern bool L2CA_UseLatencyMode(const RawAddress& bd_addr, + bool use_latency_mode); + /******************************************************************************* * * Function L2CA_SetAclPriority diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc index 6eedcbb3bf4..a7eaf6accac 100644 --- a/system/stack/l2cap/l2c_api.cc +++ b/system/stack/l2cap/l2c_api.cc @@ -1027,6 +1027,29 @@ uint8_t L2CA_SetTraceLevel(uint8_t new_level) { return (l2cb.l2cap_trace_level); } +/******************************************************************************* + * + * Function L2CA_UseLatencyMode + * + * Description Sets acl use latency mode. + * + * Returns true if a valid channel, else false + * + ******************************************************************************/ +bool L2CA_UseLatencyMode(const RawAddress& bd_addr, bool use_latency_mode) { + /* Find the link control block for the acl channel */ + tL2C_LCB* p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_BR_EDR); + if (p_lcb == nullptr) { + LOG_WARN("L2CAP - no LCB for L2CA_SetUseLatencyMode, BDA: %s", + bd_addr.ToString().c_str()); + return false; + } + LOG_INFO("BDA: %s, use_latency_mode: %s", bd_addr.ToString().c_str(), + use_latency_mode ? "true" : "false"); + p_lcb->use_latency_mode = use_latency_mode; + return true; +} + /******************************************************************************* * * Function L2CA_SetAclPriority diff --git a/system/stack/l2cap/l2c_int.h b/system/stack/l2cap/l2c_int.h index 39ba1eca1ca..b6f70ae928c 100644 --- a/system/stack/l2cap/l2c_int.h +++ b/system/stack/l2cap/l2c_int.h @@ -497,6 +497,8 @@ typedef struct t_l2c_linkcb { return false; } + bool use_latency_mode = false; + tL2C_CCB* p_fixed_ccbs[L2CAP_NUM_FIXED_CHNLS]; private: diff --git a/system/test/mock/mock_bta_av_api.cc b/system/test/mock/mock_bta_av_api.cc index 4c4e93d3c06..6017d032ed0 100644 --- a/system/test/mock/mock_bta_av_api.cc +++ b/system/test/mock/mock_bta_av_api.cc @@ -93,7 +93,9 @@ void BTA_AvRemoteVendorUniqueCmd(uint8_t rc_handle, uint8_t label, uint8_t buf_len) { mock_function_count_map[__func__]++; } -void BTA_AvStart(tBTA_AV_HNDL handle) { mock_function_count_map[__func__]++; } +void BTA_AvStart(tBTA_AV_HNDL handle, bool use_latency_mode) { + mock_function_count_map[__func__]++; +} void BTA_AvStop(tBTA_AV_HNDL handle, bool suspend) { mock_function_count_map[__func__]++; } @@ -105,3 +107,6 @@ void BTA_AvVendorRsp(uint8_t rc_handle, uint8_t label, tBTA_AV_CODE rsp_code, uint8_t* p_data, uint16_t len, uint32_t company_id) { mock_function_count_map[__func__]++; } +void BTA_AvSetLatency(tBTA_AV_HNDL handle, bool is_low_latency) { + mock_function_count_map[__func__]++; +} diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc index 96d4eefd2c9..81e71b0f8b2 100644 --- a/system/test/mock/mock_stack_l2cap_api.cc +++ b/system/test/mock/mock_stack_l2cap_api.cc @@ -83,6 +83,7 @@ struct L2CA_DisconnectLECocReq L2CA_DisconnectLECocReq; struct L2CA_GetRemoteCid L2CA_GetRemoteCid; struct L2CA_SetIdleTimeoutByBdAddr L2CA_SetIdleTimeoutByBdAddr; struct L2CA_SetTraceLevel L2CA_SetTraceLevel; +struct L2CA_UseLatencyMode L2CA_UseLatencyMode; struct L2CA_SetAclPriority L2CA_SetAclPriority; struct L2CA_SetTxPriority L2CA_SetTxPriority; struct L2CA_GetPeerFeatures L2CA_GetPeerFeatures; @@ -210,6 +211,11 @@ uint8_t L2CA_SetTraceLevel(uint8_t new_level) { mock_function_count_map[__func__]++; return test::mock::stack_l2cap_api::L2CA_SetTraceLevel(new_level); } +bool L2CA_UseLatencyMode(const RawAddress& bd_addr, bool use_latency_mode) { + mock_function_count_map[__func__]++; + return test::mock::stack_l2cap_api::L2CA_UseLatencyMode(bd_addr, + use_latency_mode); +} bool L2CA_SetAclPriority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority) { mock_function_count_map[__func__]++; return test::mock::stack_l2cap_api::L2CA_SetAclPriority(bd_addr, priority); diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h index 6f682fc7db4..b3e52b5e8f9 100644 --- a/system/test/mock/mock_stack_l2cap_api.h +++ b/system/test/mock/mock_stack_l2cap_api.h @@ -302,6 +302,17 @@ struct L2CA_SetTraceLevel { uint8_t operator()(uint8_t new_level) { return body(new_level); }; }; extern struct L2CA_SetTraceLevel L2CA_SetTraceLevel; +// Name: L2CA_UseLatencyMode +// Params: const RawAddress& bd_addr, bool use_latency_mode +// Returns: bool +struct L2CA_UseLatencyMode { + std::function body{ + [](const RawAddress& bd_addr, bool use_latency_mode) { return false; }}; + bool operator()(const RawAddress& bd_addr, bool use_latency_mode) { + return body(bd_addr, use_latency_mode); + }; +}; +extern struct L2CA_UseLatencyMode L2CA_UseLatencyMode; // Name: L2CA_SetAclPriority // Params: const RawAddress& bd_addr, tL2CAP_PRIORITY priority // Returns: bool -- GitLab From 9a115540c8dfaef68122b1389f491d8368484e97 Mon Sep 17 00:00:00 2001 From: kuanyuhuang Date: Wed, 3 Aug 2022 11:38:38 +0000 Subject: [PATCH 015/998] Support setting BT controller Low Latency mode(2/2) Handle bta latency event and set acl priority and latency by sending vendor vsc. Bug: 240637363 Bug: 223126227 Tag: #feature Ignore-AOSP-First: TM QPR1 feature Test: test on sink device support dynamic spatial audio Change-Id: I55188cc7a316f213156c39d2985420f0617421e2 --- system/bta/av/bta_av_act.cc | 19 ++++ system/bta/av/bta_av_int.h | 1 + system/bta/av/bta_av_main.cc | 3 + system/stack/include/hcidefs.h | 8 +- system/stack/include/l2c_api.h | 18 ++++ system/stack/l2cap/l2c_api.cc | 15 +++ system/stack/l2cap/l2c_int.h | 13 +++ system/stack/l2cap/l2c_utils.cc | 115 ++++++++++++++++++++--- system/test/mock/mock_stack_l2cap_api.cc | 5 + system/test/mock/mock_stack_l2cap_api.h | 13 ++- 10 files changed, 196 insertions(+), 14 deletions(-) diff --git a/system/bta/av/bta_av_act.cc b/system/bta/av/bta_av_act.cc index 9d3c90d9cf1..ebb035348d8 100644 --- a/system/bta/av/bta_av_act.cc +++ b/system/bta/av/bta_av_act.cc @@ -1352,6 +1352,25 @@ void bta_av_set_use_latency_mode(tBTA_AV_SCB* p_scb, bool use_latency_mode) { L2CA_UseLatencyMode(p_scb->PeerAddress(), use_latency_mode); } +/******************************************************************************* + * + * Function bta_av_api_set_latency + * + * Description set stream latency. + * + * Returns void + * + ******************************************************************************/ +void bta_av_api_set_latency(tBTA_AV_DATA* p_data) { + tBTA_AV_SCB* p_scb = + bta_av_hndl_to_scb(p_data->api_set_latency.hdr.layer_specific); + + tL2CAP_LATENCY latency = p_data->api_set_latency.is_low_latency + ? L2CAP_LATENCY_LOW + : L2CAP_LATENCY_NORMAL; + L2CA_SetAclLatency(p_scb->PeerAddress(), latency); +} + /** * Find the index for the free LCB entry to use. * diff --git a/system/bta/av/bta_av_int.h b/system/bta/av/bta_av_int.h index 49b3ce432b8..acd2ad6905e 100644 --- a/system/bta/av/bta_av_int.h +++ b/system/bta/av/bta_av_int.h @@ -741,6 +741,7 @@ extern bool bta_av_link_role_ok(tBTA_AV_SCB* p_scb, uint8_t bits); extern void bta_av_api_disconnect(tBTA_AV_DATA* p_data); extern void bta_av_set_use_latency_mode(tBTA_AV_SCB* p_scb, bool use_latency_mode); +extern void bta_av_api_set_latency(tBTA_AV_DATA* p_data); extern void bta_av_sig_chg(tBTA_AV_DATA* p_data); extern void bta_av_signalling_timer(tBTA_AV_DATA* p_data); extern void bta_av_rc_disc_done(tBTA_AV_DATA* p_data); diff --git a/system/bta/av/bta_av_main.cc b/system/bta/av/bta_av_main.cc index d20c1c79242..b4f9e8e63e5 100644 --- a/system/bta/av/bta_av_main.cc +++ b/system/bta/av/bta_av_main.cc @@ -1105,6 +1105,9 @@ static void bta_av_non_state_machine_event(uint16_t event, case BTA_AV_API_DISCONNECT_EVT: bta_av_api_disconnect(p_data); break; + case BTA_AV_API_SET_LATENCY_EVT: + bta_av_api_set_latency(p_data); + break; case BTA_AV_CI_SRC_DATA_READY_EVT: bta_av_ci_data(p_data); break; diff --git a/system/stack/include/hcidefs.h b/system/stack/include/hcidefs.h index b5e0164ea0c..2fdc46ffab4 100644 --- a/system/stack/include/hcidefs.h +++ b/system/stack/include/hcidefs.h @@ -888,9 +888,13 @@ typedef struct { /* Parameter information for HCI_BRCM_SET_ACL_PRIORITY */ #define HCI_BRCM_ACL_PRIORITY_PARAM_SIZE 3 -#define HCI_BRCM_ACL_PRIORITY_LOW 0x00 -#define HCI_BRCM_ACL_PRIORITY_HIGH 0xFF #define HCI_BRCM_SET_ACL_PRIORITY (0x0057 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_BRCM_ACL_HIGH_PRIORITY_TO_NORMAL_PRIORITY 0x00 +#define HCI_BRCM_ACL_NORMAL_PRIORITY_TO_HIGH_PRIORITY 0xFF +#define HCI_BRCM_ACL_NORMAL_PRIORITY_TO_HIGH_PRIORITY_LOW_LATENCY 0xF3 +#define HCI_BRCM_ACL_HIGH_PRIORITY_LOW_LATENCY_TO_NORMAL_PRIORITY 0xF2 +#define HCI_BRCM_ACL_HIGH_PRIORITY_TO_HIGH_PRIORITY_LOW_LATENCY 0xF1 +#define HCI_BRCM_ACL_HIGH_PRIORITY_LOW_LATENCY_TO_HIGH_PRIORITY 0xF0 #define LMP_COMPID_GOOGLE 0xE0 diff --git a/system/stack/include/l2c_api.h b/system/stack/include/l2c_api.h index 1a2bc407b09..f43b7543cd8 100644 --- a/system/stack/include/l2c_api.h +++ b/system/stack/include/l2c_api.h @@ -63,6 +63,12 @@ typedef enum : uint8_t { L2CAP_PRIORITY_HIGH = 1, } tL2CAP_PRIORITY; +/* Values for priority parameter to L2CA_SetAclLatency */ +typedef enum : uint8_t { + L2CAP_LATENCY_NORMAL = 0, + L2CAP_LATENCY_LOW = 1, +} tL2CAP_LATENCY; + /* Values for priority parameter to L2CA_SetTxPriority */ #define L2CAP_CHNL_PRIORITY_HIGH 0 #define L2CAP_CHNL_PRIORITY_LOW 2 @@ -660,6 +666,18 @@ extern bool L2CA_UseLatencyMode(const RawAddress& bd_addr, extern bool L2CA_SetAclPriority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority); +/******************************************************************************* + * + * Function L2CA_SetAclLatency + * + * Description Sets the transmission latency for a channel. + * + * Returns true if a valid channel, else false + * + ******************************************************************************/ +extern bool L2CA_SetAclLatency(const RawAddress& bd_addr, + tL2CAP_LATENCY latency); + /******************************************************************************* * * Function L2CA_SetTxPriority diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc index a7eaf6accac..ef65af220fd 100644 --- a/system/stack/l2cap/l2c_api.cc +++ b/system/stack/l2cap/l2c_api.cc @@ -1071,6 +1071,21 @@ bool L2CA_SetAclPriority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority) { return (l2cu_set_acl_priority(bd_addr, priority, false)); } +/******************************************************************************* + * + * Function L2CA_SetAclLatency + * + * Description Sets the transmission latency for a channel. + * + * Returns true if a valid channel, else false + * + ******************************************************************************/ +bool L2CA_SetAclLatency(const RawAddress& bd_addr, tL2CAP_LATENCY latency) { + LOG_INFO("BDA: %s, latency: %s", bd_addr.ToString().c_str(), + std::to_string(latency).c_str()); + return l2cu_set_acl_latency(bd_addr, latency); +} + /******************************************************************************* * * Function L2CA_SetTxPriority diff --git a/system/stack/l2cap/l2c_int.h b/system/stack/l2cap/l2c_int.h index b6f70ae928c..e3d70c703bb 100644 --- a/system/stack/l2cap/l2c_int.h +++ b/system/stack/l2cap/l2c_int.h @@ -498,6 +498,17 @@ typedef struct t_l2c_linkcb { } bool use_latency_mode = false; + tL2CAP_LATENCY preset_acl_latency = L2CAP_LATENCY_NORMAL; + tL2CAP_LATENCY acl_latency = L2CAP_LATENCY_NORMAL; + bool is_normal_latency() const { return acl_latency == L2CAP_LATENCY_NORMAL; } + bool is_low_latency() const { return acl_latency == L2CAP_LATENCY_LOW; } + bool set_latency(tL2CAP_LATENCY latency) { + if (acl_latency != latency) { + acl_latency = latency; + return true; + } + return false; + } tL2C_CCB* p_fixed_ccbs[L2CAP_NUM_FIXED_CHNLS]; @@ -682,6 +693,8 @@ extern tL2C_LCB* l2cu_find_lcb_by_handle(uint16_t handle); extern bool l2cu_set_acl_priority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority, bool reset_after_rs); +extern bool l2cu_set_acl_latency(const RawAddress& bd_addr, + tL2CAP_LATENCY latency); extern void l2cu_enqueue_ccb(tL2C_CCB* p_ccb); extern void l2cu_dequeue_ccb(tL2C_CCB* p_ccb); diff --git a/system/stack/l2cap/l2c_utils.cc b/system/stack/l2cap/l2c_utils.cc index 4aa780a0bb0..714e6be4f23 100755 --- a/system/stack/l2cap/l2c_utils.cc +++ b/system/stack/l2cap/l2c_utils.cc @@ -37,6 +37,8 @@ #include "stack/include/bt_hdr.h" #include "stack/include/btm_api.h" #include "stack/include/hci_error_code.h" +#include "stack/include/hcidefs.h" +#include "stack/include/l2c_api.h" #include "stack/include/l2cdefs.h" #include "stack/l2cap/l2c_int.h" #include "types/raw_address.h" @@ -2214,24 +2216,47 @@ bool l2cu_lcb_disconnecting(void) { /******************************************************************************* * - * Function l2cu_set_acl_priority_brcm + * Function l2cu_set_acl_priority_latency_brcm * - * Description Sends a VSC to set the ACL priority on Broadcom chip. + * Description Sends a VSC to set the ACL priority and recorded latency on + * Broadcom chip. * * Returns void * ******************************************************************************/ -static void l2cu_set_acl_priority_brcm(uint16_t handle, - tL2CAP_PRIORITY priority) { - uint8_t* pp; - uint8_t command[HCI_BRCM_ACL_PRIORITY_PARAM_SIZE]; +static void l2cu_set_acl_priority_latency_brcm(tL2C_LCB* p_lcb, + tL2CAP_PRIORITY priority) { uint8_t vs_param; + if (priority == L2CAP_PRIORITY_HIGH) { + // priority normal to high, if using latency mode check preset latency + LOG_INFO( + "Set ACL from priority: %d latency: %d to priority: %d latency: %d", + p_lcb->acl_priority, p_lcb->acl_latency, priority, + p_lcb->use_latency_mode ? p_lcb->preset_acl_latency + : L2CAP_LATENCY_NORMAL); + if (p_lcb->use_latency_mode && + p_lcb->preset_acl_latency == L2CAP_LATENCY_LOW) { + vs_param = HCI_BRCM_ACL_NORMAL_PRIORITY_TO_HIGH_PRIORITY_LOW_LATENCY; + p_lcb->set_latency(L2CAP_LATENCY_LOW); + } else { + vs_param = HCI_BRCM_ACL_NORMAL_PRIORITY_TO_HIGH_PRIORITY; + } + } else { + // priority high to normal, check current latency + LOG_INFO( + "Set ACL from priority: %d latency: %d to priority: %d latency: %d", + p_lcb->acl_priority, p_lcb->acl_latency, priority, + L2CAP_LATENCY_NORMAL); + vs_param = p_lcb->is_low_latency() + ? HCI_BRCM_ACL_HIGH_PRIORITY_LOW_LATENCY_TO_NORMAL_PRIORITY + : HCI_BRCM_ACL_HIGH_PRIORITY_TO_NORMAL_PRIORITY; + p_lcb->set_latency(L2CAP_LATENCY_NORMAL); + } - pp = command; - vs_param = (priority == L2CAP_PRIORITY_HIGH) ? HCI_BRCM_ACL_PRIORITY_HIGH - : HCI_BRCM_ACL_PRIORITY_LOW; - UINT16_TO_STREAM(pp, handle); + uint8_t command[HCI_BRCM_ACL_PRIORITY_PARAM_SIZE]; + uint8_t* pp = command; + UINT16_TO_STREAM(pp, p_lcb->Handle()); UINT8_TO_STREAM(pp, vs_param); BTM_VendorSpecificCommand(HCI_BRCM_SET_ACL_PRIORITY, @@ -2297,11 +2322,13 @@ bool l2cu_set_acl_priority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority, /* Use vendor specific commands to set the link priority */ switch (controller_get_interface()->get_bt_version()->manufacturer) { case LMP_COMPID_BROADCOM: - l2cu_set_acl_priority_brcm(p_lcb->Handle(), priority); + l2cu_set_acl_priority_latency_brcm(p_lcb, priority); break; + case LMP_COMPID_SYNAPTICS: l2cu_set_acl_priority_syna(p_lcb->Handle(), priority); break; + default: /* Not supported/required for other vendors */ break; @@ -2316,6 +2343,72 @@ bool l2cu_set_acl_priority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority, return (true); } +/******************************************************************************* + * + * Function l2cu_set_acl_latency_brcm + * + * Description Sends a VSC to set the ACL latency on Broadcom chip. + * + * Returns void + * + ******************************************************************************/ + +static void l2cu_set_acl_latency_brcm(tL2C_LCB* p_lcb, tL2CAP_LATENCY latency) { + LOG_INFO("Set ACL latency from %d to %d", p_lcb->acl_latency, latency); + + uint8_t command[HCI_BRCM_ACL_PRIORITY_PARAM_SIZE]; + uint8_t* pp = command; + uint8_t vs_param = + latency == L2CAP_LATENCY_LOW + ? HCI_BRCM_ACL_HIGH_PRIORITY_TO_HIGH_PRIORITY_LOW_LATENCY + : HCI_BRCM_ACL_HIGH_PRIORITY_LOW_LATENCY_TO_HIGH_PRIORITY; + UINT16_TO_STREAM(pp, p_lcb->Handle()); + UINT8_TO_STREAM(pp, vs_param); + + BTM_VendorSpecificCommand(HCI_BRCM_SET_ACL_PRIORITY, + HCI_BRCM_ACL_PRIORITY_PARAM_SIZE, command, NULL); +} + +/******************************************************************************* + * + * Function l2cu_set_acl_latency + * + * Description Sets the transmission latency for a channel. + * + * Returns true if a valid channel, else false + * + ******************************************************************************/ + +bool l2cu_set_acl_latency(const RawAddress& bd_addr, tL2CAP_LATENCY latency) { + LOG_INFO("Set ACL low latency: %d", latency); + + /* Find the link control block for the acl channel */ + tL2C_LCB* p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_BR_EDR); + + if (p_lcb == nullptr) { + LOG_WARN("Set latency failed: LCB is null"); + return false; + } + /* only change controller's latency when stream using latency mode */ + if (p_lcb->use_latency_mode && p_lcb->is_high_priority() && + latency != p_lcb->acl_latency) { + switch (controller_get_interface()->get_bt_version()->manufacturer) { + case LMP_COMPID_BROADCOM: + l2cu_set_acl_latency_brcm(p_lcb, latency); + break; + + default: + /* Not supported/required for other vendors */ + break; + } + p_lcb->set_latency(latency); + } + /* save the latency mode even if acl does not use latency mode or start*/ + p_lcb->preset_acl_latency = latency; + + return true; +} + /****************************************************************************** * * Function l2cu_set_non_flushable_pbf diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc index 81e71b0f8b2..450753c4cc6 100644 --- a/system/test/mock/mock_stack_l2cap_api.cc +++ b/system/test/mock/mock_stack_l2cap_api.cc @@ -85,6 +85,7 @@ struct L2CA_SetIdleTimeoutByBdAddr L2CA_SetIdleTimeoutByBdAddr; struct L2CA_SetTraceLevel L2CA_SetTraceLevel; struct L2CA_UseLatencyMode L2CA_UseLatencyMode; struct L2CA_SetAclPriority L2CA_SetAclPriority; +struct L2CA_SetAclLatency L2CA_SetAclLatency; struct L2CA_SetTxPriority L2CA_SetTxPriority; struct L2CA_GetPeerFeatures L2CA_GetPeerFeatures; struct L2CA_RegisterFixedChannel L2CA_RegisterFixedChannel; @@ -220,6 +221,10 @@ bool L2CA_SetAclPriority(const RawAddress& bd_addr, tL2CAP_PRIORITY priority) { mock_function_count_map[__func__]++; return test::mock::stack_l2cap_api::L2CA_SetAclPriority(bd_addr, priority); } +bool L2CA_SetAclLatency(const RawAddress& bd_addr, tL2CAP_LATENCY latency) { + mock_function_count_map[__func__]++; + return test::mock::stack_l2cap_api::L2CA_SetAclLatency(bd_addr, latency); +} bool L2CA_SetTxPriority(uint16_t cid, tL2CAP_CHNL_PRIORITY priority) { mock_function_count_map[__func__]++; return test::mock::stack_l2cap_api::L2CA_SetTxPriority(cid, priority); diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h index b3e52b5e8f9..fb0ef55a098 100644 --- a/system/test/mock/mock_stack_l2cap_api.h +++ b/system/test/mock/mock_stack_l2cap_api.h @@ -314,7 +314,7 @@ struct L2CA_UseLatencyMode { }; extern struct L2CA_UseLatencyMode L2CA_UseLatencyMode; // Name: L2CA_SetAclPriority -// Params: const RawAddress& bd_addr, tL2CAP_PRIORITY priority +// Params: const RawAddress& bd_addr, tL2CAP_PRIORITY priority, // Returns: bool struct L2CA_SetAclPriority { std::function body{ @@ -326,6 +326,17 @@ struct L2CA_SetAclPriority { }; }; extern struct L2CA_SetAclPriority L2CA_SetAclPriority; +// Name: L2CA_SetAclLatency +// Params: const RawAddress& bd_addr, tL2CAP_LATENCY latency +// Returns: bool +struct L2CA_SetAclLatency { + std::function body{ + [](const RawAddress& bd_addr, tL2CAP_LATENCY latency) { return false; }}; + bool operator()(const RawAddress& bd_addr, tL2CAP_LATENCY latency) { + return body(bd_addr, latency); + }; +}; +extern struct L2CA_SetAclLatency L2CA_SetAclLatency; // Name: L2CA_SetTxPriority // Params: uint16_t cid, tL2CAP_CHNL_PRIORITY priority // Returns: bool -- GitLab From a318c2c6808928c8416f10878c8686a81192a4ce Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Wed, 20 Apr 2022 14:18:59 +0000 Subject: [PATCH 016/998] Pandora: Rename Blueberry folder to Pandora Test: manual Ignore-AOSP-First: Cherry-picked from AOSP Bug: 241962982 Merged-In: I866741e35be10856eaea34ee604be90b4678f12a Change-Id: I866741e35be10856eaea34ee604be90b4678f12a --- android/{blueberry => pandora}/OWNERS | 0 android/{blueberry => pandora}/server/Android.bp | 0 android/{blueberry => pandora}/server/AndroidManifest.xml | 0 android/{blueberry => pandora}/server/README.md | 0 android/{blueberry => pandora}/server/configs/PtsBotTest.xml | 0 .../server/configs/pts_bot_tests_config.json | 0 android/{blueberry => pandora}/server/proto/blueberry/a2dp.proto | 0 android/{blueberry => pandora}/server/proto/blueberry/host.proto | 0 android/{blueberry => pandora}/server/scripts/setup.sh | 0 .../server/src/com/android/blueberry/A2dp.kt | 0 .../server/src/com/android/blueberry/Host.kt | 0 .../server/src/com/android/blueberry/Main.kt | 0 .../server/src/com/android/blueberry/Server.kt | 0 .../server/src/com/android/blueberry/Utils.kt | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename android/{blueberry => pandora}/OWNERS (100%) rename android/{blueberry => pandora}/server/Android.bp (100%) rename android/{blueberry => pandora}/server/AndroidManifest.xml (100%) rename android/{blueberry => pandora}/server/README.md (100%) rename android/{blueberry => pandora}/server/configs/PtsBotTest.xml (100%) rename android/{blueberry => pandora}/server/configs/pts_bot_tests_config.json (100%) rename android/{blueberry => pandora}/server/proto/blueberry/a2dp.proto (100%) rename android/{blueberry => pandora}/server/proto/blueberry/host.proto (100%) rename android/{blueberry => pandora}/server/scripts/setup.sh (100%) rename android/{blueberry => pandora}/server/src/com/android/blueberry/A2dp.kt (100%) rename android/{blueberry => pandora}/server/src/com/android/blueberry/Host.kt (100%) rename android/{blueberry => pandora}/server/src/com/android/blueberry/Main.kt (100%) rename android/{blueberry => pandora}/server/src/com/android/blueberry/Server.kt (100%) rename android/{blueberry => pandora}/server/src/com/android/blueberry/Utils.kt (100%) diff --git a/android/blueberry/OWNERS b/android/pandora/OWNERS similarity index 100% rename from android/blueberry/OWNERS rename to android/pandora/OWNERS diff --git a/android/blueberry/server/Android.bp b/android/pandora/server/Android.bp similarity index 100% rename from android/blueberry/server/Android.bp rename to android/pandora/server/Android.bp diff --git a/android/blueberry/server/AndroidManifest.xml b/android/pandora/server/AndroidManifest.xml similarity index 100% rename from android/blueberry/server/AndroidManifest.xml rename to android/pandora/server/AndroidManifest.xml diff --git a/android/blueberry/server/README.md b/android/pandora/server/README.md similarity index 100% rename from android/blueberry/server/README.md rename to android/pandora/server/README.md diff --git a/android/blueberry/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml similarity index 100% rename from android/blueberry/server/configs/PtsBotTest.xml rename to android/pandora/server/configs/PtsBotTest.xml diff --git a/android/blueberry/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json similarity index 100% rename from android/blueberry/server/configs/pts_bot_tests_config.json rename to android/pandora/server/configs/pts_bot_tests_config.json diff --git a/android/blueberry/server/proto/blueberry/a2dp.proto b/android/pandora/server/proto/blueberry/a2dp.proto similarity index 100% rename from android/blueberry/server/proto/blueberry/a2dp.proto rename to android/pandora/server/proto/blueberry/a2dp.proto diff --git a/android/blueberry/server/proto/blueberry/host.proto b/android/pandora/server/proto/blueberry/host.proto similarity index 100% rename from android/blueberry/server/proto/blueberry/host.proto rename to android/pandora/server/proto/blueberry/host.proto diff --git a/android/blueberry/server/scripts/setup.sh b/android/pandora/server/scripts/setup.sh similarity index 100% rename from android/blueberry/server/scripts/setup.sh rename to android/pandora/server/scripts/setup.sh diff --git a/android/blueberry/server/src/com/android/blueberry/A2dp.kt b/android/pandora/server/src/com/android/blueberry/A2dp.kt similarity index 100% rename from android/blueberry/server/src/com/android/blueberry/A2dp.kt rename to android/pandora/server/src/com/android/blueberry/A2dp.kt diff --git a/android/blueberry/server/src/com/android/blueberry/Host.kt b/android/pandora/server/src/com/android/blueberry/Host.kt similarity index 100% rename from android/blueberry/server/src/com/android/blueberry/Host.kt rename to android/pandora/server/src/com/android/blueberry/Host.kt diff --git a/android/blueberry/server/src/com/android/blueberry/Main.kt b/android/pandora/server/src/com/android/blueberry/Main.kt similarity index 100% rename from android/blueberry/server/src/com/android/blueberry/Main.kt rename to android/pandora/server/src/com/android/blueberry/Main.kt diff --git a/android/blueberry/server/src/com/android/blueberry/Server.kt b/android/pandora/server/src/com/android/blueberry/Server.kt similarity index 100% rename from android/blueberry/server/src/com/android/blueberry/Server.kt rename to android/pandora/server/src/com/android/blueberry/Server.kt diff --git a/android/blueberry/server/src/com/android/blueberry/Utils.kt b/android/pandora/server/src/com/android/blueberry/Utils.kt similarity index 100% rename from android/blueberry/server/src/com/android/blueberry/Utils.kt rename to android/pandora/server/src/com/android/blueberry/Utils.kt -- GitLab From dfe9d7d92bc612578e816c36830e18e7794e076b Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Thu, 21 Apr 2022 08:55:45 +0000 Subject: [PATCH 017/998] Pandora: Rename all references of Blueberry to Pandora Test: m PandoraServer (with the topic visibilities from Netty) Ignore-AOSP-First: Cherry-picked from AOSP Bug: 241962982 Merged-In: I92b818e6367767efa6a2b200364706e91f3228cf Change-Id: I92b818e6367767efa6a2b200364706e91f3228cf --- android/pandora/server/Android.bp | 22 +++++++-------- android/pandora/server/AndroidManifest.xml | 8 +++--- android/pandora/server/README.md | 28 +++++++++---------- android/pandora/server/configs/PtsBotTest.xml | 6 ++-- .../proto/{blueberry => pandora}/a2dp.proto | 4 +-- .../proto/{blueberry => pandora}/host.proto | 2 +- android/pandora/server/scripts/setup.sh | 2 +- .../android/{blueberry => pandora}/A2dp.kt | 8 +++--- .../android/{blueberry => pandora}/Host.kt | 8 +++--- .../android/{blueberry => pandora}/Main.kt | 4 +-- .../android/{blueberry => pandora}/Server.kt | 8 +++--- .../android/{blueberry => pandora}/Utils.kt | 2 +- 12 files changed, 51 insertions(+), 51 deletions(-) rename android/pandora/server/proto/{blueberry => pandora}/a2dp.proto (99%) rename android/pandora/server/proto/{blueberry => pandora}/host.proto (99%) rename android/pandora/server/src/com/android/{blueberry => pandora}/A2dp.kt (98%) rename android/pandora/server/src/com/android/{blueberry => pandora}/Host.kt (98%) rename android/pandora/server/src/com/android/{blueberry => pandora}/Main.kt (95%) rename android/pandora/server/src/com/android/{blueberry => pandora}/Server.kt (88%) rename android/pandora/server/src/com/android/{blueberry => pandora}/Utils.kt (99%) diff --git a/android/pandora/server/Android.bp b/android/pandora/server/Android.bp index 1831b8f8758..133f019875c 100644 --- a/android/pandora/server/Android.bp +++ b/android/pandora/server/Android.bp @@ -3,7 +3,7 @@ package { } android_test_helper_app { - name: "BlueberryServer", + name: "PandoraServer", srcs: ["src/**/*.kt"], platform_apis: true, certificate: "platform", @@ -16,8 +16,8 @@ android_test_helper_app { "guava", "opencensus-java-api", "kotlinx_coroutines", - "blueberry-grpc-java", - "blueberry-proto-java", + "pandora-grpc-java", + "pandora-proto-java", "opencensus-java-contrib-grpc-metrics", ], @@ -31,19 +31,19 @@ android_test_helper_app { android_test { name: "pts-bot", - required: ["BlueberryServer"], + required: ["PandoraServer"], test_config: "configs/PtsBotTest.xml", data: ["configs/pts_bot_tests_config.json"], } java_library { - name: "blueberry-grpc-java", + name: "pandora-grpc-java", visibility: ["//visibility:private"], srcs: [ - "proto/blueberry/*.proto", + "proto/pandora/*.proto", ], static_libs: [ - "blueberry-proto-java", + "pandora-proto-java", "grpc-java-lite", "guava", "opencensus-java-api", @@ -52,7 +52,7 @@ java_library { ], proto: { include_dirs: [ - "packages/modules/Bluetooth/android/blueberry/server/proto", + "packages/modules/Bluetooth/android/pandora/server/proto", "external/protobuf/src", ], plugin: "grpc-java-plugin", @@ -63,10 +63,10 @@ java_library { } java_library { - name: "blueberry-proto-java", + name: "pandora-proto-java", visibility: ["//visibility:private"], srcs: [ - "proto/blueberry/*.proto", + "proto/pandora/*.proto", ":libprotobuf-internal-protos", ], static_libs: [ @@ -75,7 +75,7 @@ java_library { proto: { type: "lite", include_dirs: [ - "packages/modules/Bluetooth/android/blueberry/server/proto", + "packages/modules/Bluetooth/android/pandora/server/proto", "external/protobuf/src", ], }, diff --git a/android/pandora/server/AndroidManifest.xml b/android/pandora/server/AndroidManifest.xml index d6b984c3908..4b04e755068 100644 --- a/android/pandora/server/AndroidManifest.xml +++ b/android/pandora/server/AndroidManifest.xml @@ -15,7 +15,7 @@ --> + package="com.android.pandora"> @@ -27,7 +27,7 @@ - + diff --git a/android/pandora/server/README.md b/android/pandora/server/README.md index ebeea949a1f..aad1f247e84 100644 --- a/android/pandora/server/README.md +++ b/android/pandora/server/README.md @@ -1,18 +1,18 @@ -# Blueberry Android server +# Pandora Android server -The Blueberry Android server exposes the [Blueberry test interfaces]( -go/blueberry-doc) over gRPC implemented on top of the Android Bluetooth SDK. +The Pandora Android server exposes the [Pandora test interfaces]( +go/pandora-doc) over gRPC implemented on top of the Android Bluetooth SDK. ## Getting started -Using Blueberry Android server requires to: +Using Pandora Android server requires to: * Build AOSP for your DUT, which can be either a physical device or an Android Virtual Device (AVD). * [Only for virtual tests] Build Rootcanal, the Android virtual Bluetooth Controller. * Setup your test environment. -* Build, install, and run Blueberry server. +* Build, install, and run Pandora server. * Run your tests. ### 1. Build and run AOSP code @@ -62,25 +62,25 @@ with the following steps to setup the test environment: `adb forward tcp: tcp:`. Rootcanal port number may differ depending on its configuration. It is 7200 for the AVD, and generally 6211 for physical devices. -* Forward Blueberry Android server port through ADB: +* Forward Pandora Android server port through ADB: `adb forward tcp:8999 tcp:8999`. The above steps can be done by executing the `setup.sh` helper script (the `-rootcanal` option must be used for virtual tests on a physical device). Finally, you must also make sure that the machine on which tests are executed -can access the ports of the Blueberry Android server, Rootcanal (if required), +can access the ports of the Pandora Android server, Rootcanal (if required), and ADB (if required). You can also check the usage examples provided below. -### 4. Build, install, and run Blueberry Android server +### 4. Build, install, and run Pandora Android server -* `m BlueberryServer` -* `adb install -r -g out/target/product//testcases/Blueberry/arm64/Blueberry.apk` +* `m PandoraServer` +* `adb install -r -g out/target/product//testcases/Pandora/arm64/Pandora.apk` * Start the instrumented app: -* `adb shell am instrument -w -e Debug false com.android.blueberry/.Server` +* `adb shell am instrument -w -e Debug false com.android.pandora/.Server` ### 5. Run your tests @@ -93,13 +93,13 @@ Here are some usage examples: * **DUT**: physical **Test type**: virtual **Test executer**: remote instance (for instance a Cloudtop) accessed via SSH - **Blueberry Android server repository location**: local machine (typically + **Pandora Android server repository location**: local machine (typically using Android Studio) * On your local machine: `./setup.sh --rootcanal`. * On your local machine: build and install the app on your DUT. * Log on your remote instance, and forward Rootcanal port (6211, may change - depending on your build) and Blueberry Android server (8999) port: + depending on your build) and Pandora Android server (8999) port: `ssh -R 6211:localhost:6211 -R 8999:localhost:8999 `. Optionnally, you can also share ADB port to your remote instance (if needed) by adding `-R 5037:localhost:5037` to the command. @@ -108,7 +108,7 @@ Here are some usage examples: * **DUT**: virtual (running in remote instance) **Test type**: virtual **Test executer**: remote instance - **Blueberry Android server repository location**: remote instance + **Pandora Android server repository location**: remote instance On your remote instance: * `./setup.sh`. diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index 7ee909a9094..47a89e33731 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -1,15 +1,15 @@ - - - + diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index 1e988914103..248ac5eae64 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -22,10 +22,10 @@ "AVDTP/SRC/ACP/SIG/SMG/BV-12-C", "AVDTP/SRC/ACP/SIG/SMG/BV-16-C", "AVDTP/SRC/ACP/SIG/SMG/BV-18-C", + "AVDTP/SRC/ACP/SIG/SMG/BV-20-C", "AVDTP/SRC/ACP/SIG/SMG/BV-22-C", "AVDTP/SRC/ACP/SIG/SMG/BV-24-C", "AVDTP/SRC/ACP/SIG/SMG/BV-26-C", - "AVDTP/SRC/ACP/SIG/SMG/BV-14-C", "AVDTP/SRC/ACP/SIG/SMG/ESR04/BI-28-C", "AVDTP/SRC/ACP/SIG/SYN/BV-06-C", "AVDTP/SRC/ACP/TRA/BTR/BI-01-C", @@ -56,6 +56,7 @@ "A2DP/SRC/SYN/BV-02-I", "AVDTP/SRC/ACP/SIG/SMG/BI-11-C", "AVDTP/SRC/ACP/SIG/SMG/BI-23-C", + "AVDTP/SRC/ACP/SIG/SMG/BV-14-C", "AVDTP/SRC/ACP/SIG/SMG/ESR05/BV-14-C", "AVDTP/SRC/INT/SIG/SMG/BV-11-C", "AVDTP/SRC/INT/SIG/SMG/BV-13-C", diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt index e4e6472b3cb..243db1e1028 100644 --- a/android/pandora/server/src/com/android/pandora/Utils.kt +++ b/android/pandora/server/src/com/android/pandora/Utils.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout /** * Creates a cold flow of intents based on an intent filter. If used multiple times in a same class, @@ -43,11 +44,11 @@ import kotlinx.coroutines.runBlocking @kotlinx.coroutines.ExperimentalCoroutinesApi fun intentFlow(context: Context, intentFilter: IntentFilter) = callbackFlow { val broadcastReceiver: BroadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - trySendBlocking(intent) + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + trySendBlocking(intent) + } } - } context.registerReceiver(broadcastReceiver, intentFilter) awaitClose { context.unregisterReceiver(broadcastReceiver) } @@ -60,6 +61,8 @@ fun intentFlow(context: Context, intentFilter: IntentFilter) = callbackFlow { * @param T the type of gRPC response. * @param scope coroutine scope used to run the coroutine. * @param responseObserver the gRPC stream observer on which to send the response. + * @param timeout the duration in seconds after which the coroutine is automatically cancelled and + * returns a timeout error. Default: 60s. * @param block the suspended function to execute to get the response. * @return reference to the coroutine as a Job. * @@ -77,15 +80,18 @@ fun intentFlow(context: Context, intentFilter: IntentFilter) = callbackFlow { */ @kotlinx.coroutines.ExperimentalCoroutinesApi fun grpcUnary( - scope: CoroutineScope, - responseObserver: StreamObserver, - block: suspend () -> T + scope: CoroutineScope, + responseObserver: StreamObserver, + timeout: Long = 60, + block: suspend () -> T ): Job { return scope.launch { try { - val response = block() - responseObserver.onNext(response) - responseObserver.onCompleted() + withTimeout(timeout * 1000) { + val response = block() + responseObserver.onNext(response) + responseObserver.onCompleted() + } } catch (e: Throwable) { e.printStackTrace() responseObserver.onError(e) @@ -112,12 +118,12 @@ fun getProfileProxy(context: Context, profile: Int): T { val flow = callbackFlow { val serviceListener = - object : BluetoothProfile.ServiceListener { - override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { - trySendBlocking(proxy) + object : BluetoothProfile.ServiceListener { + override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { + trySendBlocking(proxy) + } + override fun onServiceDisconnected(profile: Int) {} } - override fun onServiceDisconnected(profile: Int) {} - } bluetoothAdapter.getProfileProxy(context, serviceListener, profile) -- GitLab From db84438fb8b7c7103dd86043154c446ee7fca52a Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Thu, 26 May 2022 11:22:01 -0700 Subject: [PATCH 025/998] [LSC] Add LOCAL_LICENSE_KINDS to packages/modules/Bluetooth Added SPDX-license-identifier-Apache-2.0 to: android/pandora/mmi2grpc/Android.bp Bug: 68860345 Bug: 151177513 Bug: 151953481 Bug: 241962982 Test: m all Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I0b2780773ef3710b1885f9c80420002393af56ba Change-Id: I0b2780773ef3710b1885f9c80420002393af56ba --- android/pandora/mmi2grpc/Android.bp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/android/pandora/mmi2grpc/Android.bp b/android/pandora/mmi2grpc/Android.bp index f9450a43e2a..e4cfd572324 100644 --- a/android/pandora/mmi2grpc/Android.bp +++ b/android/pandora/mmi2grpc/Android.bp @@ -1,3 +1,22 @@ +package { + default_applicable_licenses: [ + "packages_modules_Bluetooth_android_pandora_mmi2grpc_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "packages_modules_Bluetooth_android_pandora_mmi2grpc_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + genrule { name: "protoc-gen-mmi2grpc-python-src", srcs: ["_build/protoc-gen-custom_grpc"], -- GitLab From c8e06ea33127a315e9538b50924316176edbd321 Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Fri, 27 May 2022 16:15:47 +0000 Subject: [PATCH 026/998] PTS-bot: freeze protobuf python package 4.21.0 version is introducing breaking changes, so freeze it to 3.20.1 for now. Test: atest pts-bot Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Ie151b67b7340c8a380ee5b80afad5d0b61b22283 Change-Id: Ie151b67b7340c8a380ee5b80afad5d0b61b22283 --- android/pandora/server/configs/PtsBotTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index 0e870e481a2..fce80a78073 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -11,7 +11,7 @@ -- GitLab From 99703b020fbe7c297c27854ceb5fa02b4f563894 Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Sat, 4 Jun 2022 01:01:35 +0000 Subject: [PATCH 027/998] PTS-bot: fix AVDTP/SRC/INT/SIG/SMG/BV-17-C Add a delay between BluetoothA2dp.STATE_CONNECTED and audiotrack.play() because sometimes the AVDTP start request is never sent. Bug: 234891800 Bug: 241962982 Test: atest pts-bot:AVDTP/SRC/INT/SIG/SMG/BV-17-C -v --iterations=30 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Ie5d28789acff428d99a78f67c6a933dc3c142b28 Change-Id: Ie5d28789acff428d99a78f67c6a933dc3c142b28 --- android/pandora/mmi2grpc/mmi2grpc/__init__.py | 2 +- android/pandora/server/src/com/android/pandora/A2dp.kt | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py index 2fd8603feb5..6385e71d286 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py +++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py @@ -95,7 +95,7 @@ class IUT: description: MMI description. style: MMI popup style, unused for now. """ - print(f'{profile} mmi: {description}', file=sys.stderr) + print(f'{profile} mmi: {interaction}', file=sys.stderr) # Handles A2DP and AVDTP MMIs. if profile in ('A2DP', 'AVDTP'): diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt index 9dd95f8be67..bdb8e204219 100644 --- a/android/pandora/server/src/com/android/pandora/A2dp.kt +++ b/android/pandora/server/src/com/android/pandora/A2dp.kt @@ -33,6 +33,7 @@ import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter @@ -118,6 +119,10 @@ class A2dp(val context: Context) : A2DPImplBase() { throw Status.UNKNOWN.asException() } } + + //TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. + delay(2000L) + val source = Source.newBuilder().setCookie(request.connection.cookie).build() OpenSourceResponse.newBuilder().setSource(source).build() } @@ -152,6 +157,10 @@ class A2dp(val context: Context) : A2DPImplBase() { throw Status.UNKNOWN.asException() } } + + //TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. + delay(2000L) + val source = Source.newBuilder().setCookie(request.connection.cookie).build() WaitSourceResponse.newBuilder().setSource(source).build() } -- GitLab From 9a54c4ed84abe2907a0690c8f0ea6a27aee0a0e5 Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Sat, 4 Jun 2022 01:07:02 +0000 Subject: [PATCH 028/998] PTS-bot: fix reset Add a delay between disable and enable in reset to avoid Bluetooth to be disabled unwillingly after. Bug: 234892968 Bug: 241962982 Test: atest pts-bot -v Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I72d0f4366de7dca330c5e10c0bab4f4fab7e1500 Change-Id: I72d0f4366de7dca330c5e10c0bab4f4fab7e1500 --- android/pandora/server/src/com/android/pandora/Host.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/pandora/server/src/com/android/pandora/Host.kt b/android/pandora/server/src/com/android/pandora/Host.kt index 0554ed078e5..2ba5d268b11 100644 --- a/android/pandora/server/src/com/android/pandora/Host.kt +++ b/android/pandora/server/src/com/android/pandora/Host.kt @@ -33,6 +33,7 @@ import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter @@ -87,6 +88,10 @@ class Host(private val context: Context, private val server: Server) : HostImplB bluetoothAdapter.disable() stateFlow.filter { it == BluetoothAdapter.STATE_OFF }.first() } + + //TODO: b/234892968 + delay(2000L) + bluetoothAdapter.enable() stateFlow.filter { it == BluetoothAdapter.STATE_ON }.first() -- GitLab From 4ff5705230356efd000aa765ebe4d7e63cce2bc7 Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Mon, 6 Jun 2022 20:36:17 +0000 Subject: [PATCH 029/998] PTS-bot: Build AudioTrack at first Start - Prevent a server crash when the audioFlinger could not create the track. If so, the server now respond with a gRPC error instead of crashing the whole server. - Apply ktfmt Bug: 241962982 Test: atest pts-bot --iterations=10 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I4b93e6a209c6851bea693b89e48a9c58c6b98d9e Change-Id: I4b93e6a209c6851bea693b89e48a9c58c6b98d9e --- .../server/src/com/android/pandora/A2dp.kt | 66 +++++++++++-------- .../server/src/com/android/pandora/Host.kt | 6 +- .../server/src/com/android/pandora/Utils.kt | 26 ++++---- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt index bdb8e204219..ad1668658ba 100644 --- a/android/pandora/server/src/com/android/pandora/A2dp.kt +++ b/android/pandora/server/src/com/android/pandora/A2dp.kt @@ -26,8 +26,6 @@ import android.content.Intent import android.content.IntentFilter import android.media.* import android.util.Log -import pandora.A2DPGrpc.A2DPImplBase -import pandora.A2dpProto.* import io.grpc.Status import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope @@ -40,6 +38,8 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn +import pandora.A2DPGrpc.A2DPImplBase +import pandora.A2dpProto.* @kotlinx.coroutines.ExperimentalCoroutinesApi class A2dp(val context: Context) : A2DPImplBase() { @@ -56,24 +56,7 @@ class A2dp(val context: Context) : A2DPImplBase() { private val bluetoothAdapter = bluetoothManager.adapter private val bluetoothA2dp = getProfileProxy(context, BluetoothProfile.A2DP) - private val audioTrack: AudioTrack = - AudioTrack.Builder() - .setAudioAttributes( - AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .build() - ) - .setAudioFormat( - AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setSampleRate(44100) - .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) - .build() - ) - .setTransferMode(AudioTrack.MODE_STREAM) - .setBufferSizeInBytes(44100 * 2 * 2) - .build() + private var audioTrack: AudioTrack? = null init { scope = CoroutineScope(Dispatchers.Default) @@ -89,6 +72,28 @@ class A2dp(val context: Context) : A2DPImplBase() { scope.cancel() } + fun buildAudioTrack(): AudioTrack? { + audioTrack = + AudioTrack.Builder() + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build() + ) + .setAudioFormat( + AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(44100) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) + .build() + ) + .setTransferMode(AudioTrack.MODE_STREAM) + .setBufferSizeInBytes(44100 * 2 * 2) + .build() + return audioTrack + } + override fun openSource( request: OpenSourceRequest, responseObserver: StreamObserver @@ -120,7 +125,7 @@ class A2dp(val context: Context) : A2DPImplBase() { } } - //TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. + // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. delay(2000L) val source = Source.newBuilder().setCookie(request.connection.cookie).build() @@ -158,7 +163,7 @@ class A2dp(val context: Context) : A2DPImplBase() { } } - //TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. + // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too early. delay(2000L) val source = Source.newBuilder().setCookie(request.connection.cookie).build() @@ -168,6 +173,9 @@ class A2dp(val context: Context) : A2DPImplBase() { override fun start(request: StartRequest, responseObserver: StreamObserver) { grpcUnary(scope, responseObserver) { + if (audioTrack == null) { + audioTrack = buildAudioTrack() + } val address = request.source.cookie.toByteArray().decodeToString() val device = bluetoothAdapter.getRemoteDevice(address) Log.i(TAG, "start: address=$address") @@ -177,7 +185,7 @@ class A2dp(val context: Context) : A2DPImplBase() { throw Status.UNKNOWN.asException() } - audioTrack.play() + audioTrack!!.play() // If A2dp is not already playing, wait for it if (!bluetoothA2dp.isA2dpPlaying(device)) { @@ -218,7 +226,7 @@ class A2dp(val context: Context) : A2DPImplBase() { } .map { it.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothAdapter.ERROR) } - audioTrack.pause() + audioTrack!!.pause() a2dpPlayingStateFlow.filter { it == BluetoothA2dp.STATE_NOT_PLAYING }.first() SuspendResponse.getDefaultInstance() } @@ -274,8 +282,10 @@ class A2dp(val context: Context) : A2DPImplBase() { ): StreamObserver { Log.i(TAG, "playbackAudio") - if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) { - responseObserver.onError(Status.UNKNOWN.withDescription("AudioTrack is not started").asException()) + if (audioTrack!!.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) { + responseObserver.onError( + Status.UNKNOWN.withDescription("AudioTrack is not started").asException() + ) } // Volume is maxed out to avoid any amplitude modification of the provided audio data, @@ -297,9 +307,7 @@ class A2dp(val context: Context) : A2DPImplBase() { return object : StreamObserver { override fun onNext(request: PlaybackAudioRequest) { val data = request.data.toByteArray() - val written = synchronized(audioTrack) { - audioTrack.write(data, 0, data.size) - } + val written = synchronized(audioTrack!!) { audioTrack!!.write(data, 0, data.size) } if (written != data.size) { responseObserver.onError( Status.UNKNOWN.withDescription("AudioTrack write failed").asException() diff --git a/android/pandora/server/src/com/android/pandora/Host.kt b/android/pandora/server/src/com/android/pandora/Host.kt index 2ba5d268b11..3872db5248e 100644 --- a/android/pandora/server/src/com/android/pandora/Host.kt +++ b/android/pandora/server/src/com/android/pandora/Host.kt @@ -24,8 +24,6 @@ import android.content.Intent import android.content.IntentFilter import android.net.MacAddress import android.util.Log -import pandora.HostGrpc.HostImplBase -import pandora.HostProto.* import com.google.protobuf.ByteString import com.google.protobuf.Empty import io.grpc.Status @@ -41,6 +39,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import pandora.HostGrpc.HostImplBase +import pandora.HostProto.* @kotlinx.coroutines.ExperimentalCoroutinesApi class Host(private val context: Context, private val server: Server) : HostImplBase() { @@ -89,7 +89,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB stateFlow.filter { it == BluetoothAdapter.STATE_OFF }.first() } - //TODO: b/234892968 + // TODO: b/234892968 delay(2000L) bluetoothAdapter.enable() diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt index 243db1e1028..f1170fe6e7c 100644 --- a/android/pandora/server/src/com/android/pandora/Utils.kt +++ b/android/pandora/server/src/com/android/pandora/Utils.kt @@ -44,11 +44,11 @@ import kotlinx.coroutines.withTimeout @kotlinx.coroutines.ExperimentalCoroutinesApi fun intentFlow(context: Context, intentFilter: IntentFilter) = callbackFlow { val broadcastReceiver: BroadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - trySendBlocking(intent) - } + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + trySendBlocking(intent) } + } context.registerReceiver(broadcastReceiver, intentFilter) awaitClose { context.unregisterReceiver(broadcastReceiver) } @@ -80,10 +80,10 @@ fun intentFlow(context: Context, intentFilter: IntentFilter) = callbackFlow { */ @kotlinx.coroutines.ExperimentalCoroutinesApi fun grpcUnary( - scope: CoroutineScope, - responseObserver: StreamObserver, - timeout: Long = 60, - block: suspend () -> T + scope: CoroutineScope, + responseObserver: StreamObserver, + timeout: Long = 60, + block: suspend () -> T ): Job { return scope.launch { try { @@ -118,12 +118,12 @@ fun getProfileProxy(context: Context, profile: Int): T { val flow = callbackFlow { val serviceListener = - object : BluetoothProfile.ServiceListener { - override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { - trySendBlocking(proxy) - } - override fun onServiceDisconnected(profile: Int) {} + object : BluetoothProfile.ServiceListener { + override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { + trySendBlocking(proxy) } + override fun onServiceDisconnected(profile: Int) {} + } bluetoothAdapter.getProfileProxy(context, serviceListener, profile) -- GitLab From 98a7b1ac3d152e0420bc74e5b1f85e87ca9ac781 Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Tue, 7 Jun 2022 16:18:25 +0000 Subject: [PATCH 030/998] PTS-bot: remove A2DP/SUS/ tests Remove A2DP/SRC/SUS/BV-01-I and A2DP/SRC/SUS/BV-02-I since flakiness is too important. Test: atest pts-bot -v Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I09c1f44e702f8330e3a716e63a0cd2fb3d10f0bf Change-Id: I09c1f44e702f8330e3a716e63a0cd2fb3d10f0bf --- android/pandora/server/configs/pts_bot_tests_config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index 248ac5eae64..cb28695b0c0 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -7,8 +7,6 @@ "A2DP/SRC/SET/BV-01-I", "A2DP/SRC/SET/BV-02-I", "A2DP/SRC/SET/BV-04-I", - "A2DP/SRC/SUS/BV-01-I", - "A2DP/SRC/SUS/BV-02-I", "AVDTP/SRC/ACP/SIG/SMG/BI-05-C", "AVDTP/SRC/ACP/SIG/SMG/BI-08-C", "AVDTP/SRC/ACP/SIG/SMG/BI-14-C", @@ -53,6 +51,8 @@ "A2DP/SRC/SET/BV-03-I", "A2DP/SRC/SET/BV-05-I", "A2DP/SRC/SET/BV-06-I", + "A2DP/SRC/SUS/BV-01-I", + "A2DP/SRC/SUS/BV-02-I", "A2DP/SRC/SYN/BV-02-I", "AVDTP/SRC/ACP/SIG/SMG/BI-11-C", "AVDTP/SRC/ACP/SIG/SMG/BI-23-C", -- GitLab From a5a0731a638ed9b764441b226648e69ca6797afa Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Thu, 16 Jun 2022 23:56:52 +0000 Subject: [PATCH 031/998] PTS-bot: fix failure after reset Test: atest pts-bot -v Bug: 235492458 Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Ia21ba93cb91fd695f8c5550eba23be5da6872547 Change-Id: Ia21ba93cb91fd695f8c5550eba23be5da6872547 --- android/pandora/mmi2grpc/mmi2grpc/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py index 6385e71d286..f897f462fbb 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py +++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py @@ -70,7 +70,8 @@ class IUT: try: return Host(channel).ReadLocalAddress( wait_for_ready=True).address - except grpc.RpcError: + except grpc.RpcError or grpc._channel._InactiveRpcError: + tries += 1 if tries >= MAX_RETRIES: raise else: -- GitLab From 3fc01e57925480e7df37fad6e4c6c05e0daae9ec Mon Sep 17 00:00:00 2001 From: Thomas Girardier Date: Fri, 17 Jun 2022 23:40:19 +0000 Subject: [PATCH 032/998] PTS-bot: fix failure during reset Test: atest pts-bot -v Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I9156276ab1f71dcd17e3a2c34e1f7bf871135d67 Change-Id: I9156276ab1f71dcd17e3a2c34e1f7bf871135d67 --- android/pandora/mmi2grpc/mmi2grpc/__init__.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py index f897f462fbb..6855ffb2de9 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py +++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py @@ -56,27 +56,32 @@ class IUT: # Note: we don't keep a single gRPC channel instance in the IUT class # because reset is allowed to close the gRPC server. with grpc.insecure_channel(f'localhost:{self.port}') as channel: - Host(channel).Reset(wait_for_ready=True) + self._retry(Host(channel).Reset)(wait_for_ready=True) def __exit__(self, exc_type, exc_value, exc_traceback): self._a2dp = None - @property - def address(self) -> bytes: - """Bluetooth MAC address of the IUT.""" - with grpc.insecure_channel(f'localhost:{self.port}') as channel: + def _retry(self, func): + def wrapper(*args, **kwargs): tries = 0 while True: try: - return Host(channel).ReadLocalAddress( - wait_for_ready=True).address + return func(*args, **kwargs) except grpc.RpcError or grpc._channel._InactiveRpcError: tries += 1 if tries >= MAX_RETRIES: raise else: - print('Retry', tries, 'of', MAX_RETRIES) + print(f'Retry {func.__name__}: {tries}/{MAX_RETRIES}') time.sleep(1) + return wrapper + + @property + def address(self) -> bytes: + """Bluetooth MAC address of the IUT.""" + with grpc.insecure_channel(f'localhost:{self.port}') as channel: + return self._retry( + Host(channel).ReadLocalAddress)(wait_for_ready=True).address def interact(self, pts_address: bytes, -- GitLab From 502a266a6dbe8df93c3cf453e7d3ad12cc827261 Mon Sep 17 00:00:00 2001 From: David Duarte Date: Wed, 22 Jun 2022 15:19:37 +0000 Subject: [PATCH 033/998] Pandora: Skip AVDTP/SRC/INT/SIG/SMG/BI-35-C The test is flaky and sometimes ends with unconclusive. To reenable it, an option to retry unconclusive tests needs to be added. Test: None Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I6a6fa89918c0c5e23e4cd9908d7f5c923a8a0d2f Change-Id: I6a6fa89918c0c5e23e4cd9908d7f5c923a8a0d2f --- android/pandora/server/configs/pts_bot_tests_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index cb28695b0c0..bc0b82e6a65 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -28,7 +28,6 @@ "AVDTP/SRC/ACP/SIG/SYN/BV-06-C", "AVDTP/SRC/ACP/TRA/BTR/BI-01-C", "AVDTP/SRC/INT/SIG/SMG/BI-30-C", - "AVDTP/SRC/INT/SIG/SMG/BI-35-C", "AVDTP/SRC/INT/SIG/SMG/BI-36-C", "AVDTP/SRC/INT/SIG/SMG/BV-05-C", "AVDTP/SRC/INT/SIG/SMG/BV-07-C", @@ -58,6 +57,7 @@ "AVDTP/SRC/ACP/SIG/SMG/BI-23-C", "AVDTP/SRC/ACP/SIG/SMG/BV-14-C", "AVDTP/SRC/ACP/SIG/SMG/ESR05/BV-14-C", + "AVDTP/SRC/INT/SIG/SMG/BI-35-C", "AVDTP/SRC/INT/SIG/SMG/BV-11-C", "AVDTP/SRC/INT/SIG/SMG/BV-13-C", "AVDTP/SRC/INT/SIG/SMG/BV-23-C", -- GitLab From b2c58e449d6b64ab780d32fa1d0424b288b84fa9 Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Thu, 7 Jul 2022 22:31:27 +0000 Subject: [PATCH 034/998] Pandora: Remove warnings from deprecated functions Test: atest pts-bot:A2DP/SRC and atest pts-bot:AVDTP/SRC Ignore-AOSP-First: Cherry-picked from AOSP Bug: 241962982 Merged-In: I9d3725ae0ff7e7e4e1ee4d85905221ea11931535 Change-Id: I9d3725ae0ff7e7e4e1ee4d85905221ea11931535 --- .../server/src/com/android/pandora/A2dp.kt | 12 +++--------- .../server/src/com/android/pandora/Host.kt | 16 ++++------------ .../server/src/com/android/pandora/Utils.kt | 4 ++++ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt index ad1668658ba..17d71fdf4c0 100644 --- a/android/pandora/server/src/com/android/pandora/A2dp.kt +++ b/android/pandora/server/src/com/android/pandora/A2dp.kt @@ -191,9 +191,7 @@ class A2dp(val context: Context) : A2DPImplBase() { if (!bluetoothA2dp.isA2dpPlaying(device)) { flow .filter { it.getAction() == BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == address - } + .filter { it.getBluetoothDeviceExtra().address == address } .map { it.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothAdapter.ERROR) } .filter { it == BluetoothA2dp.STATE_PLAYING } .first() @@ -221,9 +219,7 @@ class A2dp(val context: Context) : A2DPImplBase() { val a2dpPlayingStateFlow = flow .filter { it.getAction() == BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == address - } + .filter { it.getBluetoothDeviceExtra().address == address } .map { it.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothAdapter.ERROR) } audioTrack!!.pause() @@ -265,9 +261,7 @@ class A2dp(val context: Context) : A2DPImplBase() { val a2dpConnectionStateChangedFlow = flow .filter { it.getAction() == BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == address - } + .filter { it.getBluetoothDeviceExtra().address == address } .map { it.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothAdapter.ERROR) } bluetoothA2dp.disconnect(device) diff --git a/android/pandora/server/src/com/android/pandora/Host.kt b/android/pandora/server/src/com/android/pandora/Host.kt index 3872db5248e..f16653c1df3 100644 --- a/android/pandora/server/src/com/android/pandora/Host.kt +++ b/android/pandora/server/src/com/android/pandora/Host.kt @@ -136,14 +136,10 @@ class Host(private val context: Context, private val server: Server) : HostImplB val pairingRequestIntent = flow .filter { it.getAction() == BluetoothDevice.ACTION_PAIRING_REQUEST } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == - address - } + .filter { it.getBluetoothDeviceExtra().address == address } .first() - val bluetoothDevice = - pairingRequestIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) + val bluetoothDevice = pairingRequestIntent.getBluetoothDeviceExtra() val pairingVariant = pairingRequestIntent.getIntExtra( BluetoothDevice.EXTRA_PAIRING_VARIANT, @@ -163,9 +159,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB // have been connected). flow .filter { it.getAction() == BluetoothDevice.ACTION_BOND_STATE_CHANGED } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == address - } + .filter { it.getBluetoothDeviceExtra().address == address } .map { it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) } .filter { it == BluetoothDevice.BOND_BONDED } .first() @@ -199,9 +193,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB val connectionStateChangedFlow = flow .filter { it.getAction() == BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED } - .filter { - it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE).address == address - } + .filter { it.getBluetoothDeviceExtra().address == address } .map { it.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR) } bluetoothDevice.disconnect() diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt index f1170fe6e7c..7423e871959 100644 --- a/android/pandora/server/src/com/android/pandora/Utils.kt +++ b/android/pandora/server/src/com/android/pandora/Utils.kt @@ -16,6 +16,7 @@ package com.android.pandora +import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.BroadcastReceiver @@ -133,3 +134,6 @@ fun getProfileProxy(context: Context, profile: Int): T { } return proxy } + +fun Intent.getBluetoothDeviceExtra(): BluetoothDevice = + this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) -- GitLab From f5e75e668d585730bc86c157d5c1bdc706324ecf Mon Sep 17 00:00:00 2001 From: William Escande Date: Wed, 13 Jul 2022 14:34:20 -0700 Subject: [PATCH 035/998] [PANDORA_TEST] pts-bot HFP Coverage: DIS/BV-01-I Create the HFP layer inside pts-bot. Add the first HFP test: * HFP/AG/DIS/BV-01-I Bug: 237447510 Bug: 241962982 Test: atest pts-bot:HFP/AG/DIS/BV-01-I -v Test: atest pts-bot:A2DP/SRC Test: atest pts-bot:AVDTP/SRC Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I46dfe1cf90caf1195f8d93c89185b895cb4d62c2 Change-Id: I46dfe1cf90caf1195f8d93c89185b895cb4d62c2 --- android/pandora/mmi2grpc/mmi2grpc/__init__.py | 46 ++--- android/pandora/mmi2grpc/mmi2grpc/hfp.py | 70 ++++++++ android/pandora/server/Android.bp | 21 ++- android/pandora/server/configs/PtsBotTest.xml | 2 + .../pandora/server/proto/pandora/hfp.proto | 23 +++ .../pandora/server/proto/pandora/host.proto | 11 ++ .../server/src/com/android/pandora/A2dp.kt | 6 +- .../server/src/com/android/pandora/Hfp.kt | 71 ++++++++ .../server/src/com/android/pandora/Host.kt | 167 ++++++++++++------ .../server/src/com/android/pandora/Server.kt | 10 +- .../server/src/com/android/pandora/Utils.kt | 22 ++- system/bta/ag/bta_ag_cmd.cc | 2 +- 12 files changed, 352 insertions(+), 99 deletions(-) create mode 100644 android/pandora/mmi2grpc/mmi2grpc/hfp.py create mode 100644 android/pandora/server/proto/pandora/hfp.proto create mode 100644 android/pandora/server/src/com/android/pandora/Hfp.kt diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py index 6855ffb2de9..84bd6af39da 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py +++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py @@ -11,7 +11,6 @@ # 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. - """Map Bluetooth PTS Man Machine Interface to Pandora gRPC calls.""" __version__ = "0.0.1" @@ -23,7 +22,9 @@ import sys import grpc from mmi2grpc.a2dp import A2DPProxy +from mmi2grpc.hfp import HFPProxy from mmi2grpc._helpers import format_proxy + from pandora.host_grpc import Host GRPC_PORT = 8999 @@ -36,8 +37,8 @@ class IUT: Handles MMI calls from the PTS and routes them to corresponding profile proxy which translates MMI calls to gRPC calls to the IUT. """ - def __init__( - self, test: str, args: List[str], port: int = GRPC_PORT, **kwargs): + + def __init__(self, test: str, args: List[str], port: int = GRPC_PORT, **kwargs): """Init IUT class for a given test. Args: @@ -50,6 +51,7 @@ class IUT: # Profile proxies. self._a2dp = None + self._hfp = None def __enter__(self): """Resets the IUT when starting a PTS test.""" @@ -60,8 +62,10 @@ class IUT: def __exit__(self, exc_type, exc_value, exc_traceback): self._a2dp = None + self._hfp = None def _retry(self, func): + def wrapper(*args, **kwargs): tries = 0 while True: @@ -74,22 +78,16 @@ class IUT: else: print(f'Retry {func.__name__}: {tries}/{MAX_RETRIES}') time.sleep(1) + return wrapper @property def address(self) -> bytes: """Bluetooth MAC address of the IUT.""" with grpc.insecure_channel(f'localhost:{self.port}') as channel: - return self._retry( - Host(channel).ReadLocalAddress)(wait_for_ready=True).address - - def interact(self, - pts_address: bytes, - profile: str, - test: str, - interaction: str, - description: str, - style: str, + return self._retry(Host(channel).ReadLocalAddress)(wait_for_ready=True).address + + def interact(self, pts_address: bytes, profile: str, test: str, interaction: str, description: str, style: str, **kwargs) -> str: """Routes MMI calls to corresponding profile proxy. @@ -106,18 +104,20 @@ class IUT: # Handles A2DP and AVDTP MMIs. if profile in ('A2DP', 'AVDTP'): if not self._a2dp: - self._a2dp = A2DPProxy( - grpc.insecure_channel(f'localhost:{self.port}')) - return self._a2dp.interact( - test, interaction, description, pts_address) + self._a2dp = A2DPProxy(grpc.insecure_channel(f'localhost:{self.port}')) + return self._a2dp.interact(test, interaction, description, pts_address) + # Handles HFP MMIs. + if profile in ('HFP'): + if not self._hfp: + self._hfp = HFPProxy(grpc.insecure_channel(f'localhost:{self.port}')) + return self._hfp.interact(test, interaction, description, pts_address) # Handles unsupported profiles. code = format_proxy(profile, interaction, description) - error_msg = ( - f'Missing {profile} proxy and mmi: {interaction}\n' - f'Create a {profile.lower()}.py in mmi2grpc/:\n\n{code}\n' - f'Then, instantiate the corresponding proxy in __init__.py\n' - f'Finally, create a {profile.lower()}.proto in proto/pandora/' - f'and generate the corresponding interface.') + error_msg = (f'Missing {profile} proxy and mmi: {interaction}\n' + f'Create a {profile.lower()}.py in mmi2grpc/:\n\n{code}\n' + f'Then, instantiate the corresponding proxy in __init__.py\n' + f'Finally, create a {profile.lower()}.proto in proto/pandora/' + f'and generate the corresponding interface.') assert False, error_msg diff --git a/android/pandora/mmi2grpc/mmi2grpc/hfp.py b/android/pandora/mmi2grpc/mmi2grpc/hfp.py new file mode 100644 index 00000000000..c05b2caaec3 --- /dev/null +++ b/android/pandora/mmi2grpc/mmi2grpc/hfp.py @@ -0,0 +1,70 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. +"""HFP proxy module.""" + +from mmi2grpc._helpers import assert_description +from mmi2grpc._proxy import ProfileProxy + +from pandora.hfp_grpc import HFP +from pandora.host_grpc import Host + +import sys +import threading + + +class HFPProxy(ProfileProxy): + + def __init__(self, channel): + super().__init__() + self.hfp = HFP(channel) + self.host = Host(channel) + + @assert_description + def TSC_delete_pairing_iut(self, pts_addr: bytes, **kwargs): + """ + Delete the pairing with the PTS using the Implementation Under Test + (IUT), then click Ok. + """ + + self.host.DeletePairing(address=pts_addr) + return "OK" + + @assert_description + def TSC_iut_search(self, **kwargs): + """ + Using the Implementation Under Test (IUT), perform a search for the PTS. + If found, click OK. + """ + + return "OK" + + @assert_description + def TSC_iut_connect(self, pts_addr: bytes, **kwargs): + """ + Click Ok, then make a connection request to the PTS from the + Implementation Under Test (IUT). + """ + + self.connection = self.host.Connect(address=pts_addr).connection + return "OK" + + @assert_description + def TSC_iut_disable_slc(self, pts_addr: bytes, **kwargs): + """ + Click Ok, then disable the service level connection using the + Implementation Under Test (IUT). + """ + + self.hfp.DisableSlc(address=pts_addr) + return "OK" diff --git a/android/pandora/server/Android.bp b/android/pandora/server/Android.bp index 7c5a5276e78..222f6a59f4a 100644 --- a/android/pandora/server/Android.bp +++ b/android/pandora/server/Android.bp @@ -46,17 +46,17 @@ java_library { "proto/pandora/*.proto", ], static_libs: [ - "pandora-proto-java", "grpc-java-lite", "guava", - "opencensus-java-api", - "libprotobuf-java-lite", "javax_annotation-api_1.3.2", + "libprotobuf-java-lite", + "opencensus-java-api", + "pandora-proto-java", ], proto: { include_dirs: [ - "packages/modules/Bluetooth/android/pandora/server/proto", "external/protobuf/src", + "packages/modules/Bluetooth/android/pandora/server/proto", ], plugin: "grpc-java-plugin", output_params: [ @@ -78,8 +78,8 @@ java_library { proto: { type: "lite", include_dirs: [ - "packages/modules/Bluetooth/android/pandora/server/proto", "external/protobuf/src", + "packages/modules/Bluetooth/android/pandora/server/proto", ], }, } @@ -98,13 +98,16 @@ genrule { " --python_out=$(genDir)" + " $(in)", srcs: [ + "proto/pandora/a2dp.proto", + "proto/pandora/hfp.proto", "proto/pandora/host.proto", - "proto/pandora/a2dp.proto" ], out: [ - "pandora/host_pb2.py", - "pandora/host_grpc.py", - "pandora/a2dp_pb2.py", "pandora/a2dp_grpc.py", + "pandora/a2dp_pb2.py", + "pandora/hfp_grpc.py", + "pandora/hfp_pb2.py", + "pandora/host_grpc.py", + "pandora/host_pb2.py", ] } diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index fce80a78073..69ca6c4854a 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -12,6 +12,7 @@ @@ -21,5 +22,6 @@ diff --git a/android/pandora/server/proto/pandora/hfp.proto b/android/pandora/server/proto/pandora/hfp.proto new file mode 100644 index 00000000000..3ee86c0aa59 --- /dev/null +++ b/android/pandora/server/proto/pandora/hfp.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_outer_classname = "HfpProto"; + +package pandora; + +import "pandora/host.proto"; +import "google/protobuf/wrappers.proto"; + +// Service to trigger HFP (Hands Free Profile) procedures. +service HFP { + // Disable Service level connection + rpc DisableSlc(DisableSlcRequest) returns (DisableSlcResponse); +} + +// Request of the `DisableSlc` method. +message DisableSlcRequest { + // Local Bluetooth Device Address as array of 6 bytes. + bytes address = 1; +} + +// Response of the `DisableSlc` method. +message DisableSlcResponse {} diff --git a/android/pandora/server/proto/pandora/host.proto b/android/pandora/server/proto/pandora/host.proto index a1d03f3ff2a..9fc209296ee 100644 --- a/android/pandora/server/proto/pandora/host.proto +++ b/android/pandora/server/proto/pandora/host.proto @@ -18,6 +18,8 @@ service Host { // The GRPC server might take some time to be available after // this command. rpc Reset(google.protobuf.Empty) returns (google.protobuf.Empty); + // Remove pairing + rpc DeletePairing(DeletePairingRequest) returns (DeletePairingResponse); // Create an ACL BR/EDR connection to a peer. // This should send a CreateConnection on the HCI level. // If the two devices have not established a previous bond, @@ -87,6 +89,15 @@ message WaitConnectionResponse { } } +// Request of the `DeletePairing` method. +message DeletePairingRequest { + // Local Bluetooth Device Address as array of 6 bytes. + bytes address = 1; +} + +// Response of the `DeletePairing` method. +message DeletePairingResponse {} + // Request of the `Disconnect` method. message DisconnectRequest { // Connection that should be disconnected. diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt index 17d71fdf4c0..ca7af55c67e 100644 --- a/android/pandora/server/src/com/android/pandora/A2dp.kt +++ b/android/pandora/server/src/com/android/pandora/A2dp.kt @@ -48,11 +48,9 @@ class A2dp(val context: Context) : A2DPImplBase() { private val scope: CoroutineScope private val flow: Flow - private val audioManager: AudioManager = - context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private val audioManager = context.getSystemService(AudioManager::class.java)!! - private val bluetoothManager = - context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! private val bluetoothAdapter = bluetoothManager.adapter private val bluetoothA2dp = getProfileProxy(context, BluetoothProfile.A2DP) diff --git a/android/pandora/server/src/com/android/pandora/Hfp.kt b/android/pandora/server/src/com/android/pandora/Hfp.kt new file mode 100644 index 00000000000..b10310e2aa1 --- /dev/null +++ b/android/pandora/server/src/com/android/pandora/Hfp.kt @@ -0,0 +1,71 @@ +/* + * 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.pandora + +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothHeadset +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import io.grpc.stub.StreamObserver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import pandora.HFPGrpc.HFPImplBase +import pandora.HfpProto.* + +@kotlinx.coroutines.ExperimentalCoroutinesApi +class Hfp(val context: Context) : HFPImplBase() { + private val TAG = "PandoraHfp" + + private val scope: CoroutineScope + private val flow: Flow + + private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! + private val bluetoothAdapter = bluetoothManager.adapter + + private val bluetoothHfp = getProfileProxy(context, BluetoothProfile.HEADSET) + + init { + scope = CoroutineScope(Dispatchers.Default) + + val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED) + flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly) + } + + fun deinit() { + scope.cancel() + } + + override fun disableSlc( + request: DisableSlcRequest, + responseObserver: StreamObserver + ) { + grpcUnary(scope, responseObserver) { + val device = request.address.toBluetoothDevice(bluetoothAdapter) + + bluetoothHfp.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) + + DisableSlcResponse.getDefaultInstance() + } + } +} diff --git a/android/pandora/server/src/com/android/pandora/Host.kt b/android/pandora/server/src/com/android/pandora/Host.kt index f16653c1df3..4430de98e9d 100644 --- a/android/pandora/server/src/com/android/pandora/Host.kt +++ b/android/pandora/server/src/com/android/pandora/Host.kt @@ -49,8 +49,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB private val scope: CoroutineScope private val flow: Flow - private val bluetoothManager = - context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! private val bluetoothAdapter = bluetoothManager.adapter init { @@ -75,29 +74,29 @@ class Host(private val context: Context, private val server: Server) : HostImplB override fun reset(request: Empty, responseObserver: StreamObserver) { grpcUnary(scope, responseObserver) { - Log.i(TAG, "reset") + Log.i(TAG, "reset") - bluetoothAdapter.clearBluetooth() + bluetoothAdapter.clearBluetooth() - val stateFlow = - flow.filter { it.getAction() == BluetoothAdapter.ACTION_STATE_CHANGED }.map { - it.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) - } + val stateFlow = + flow + .filter { it.getAction() == BluetoothAdapter.ACTION_STATE_CHANGED } + .map { it.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) } - if (bluetoothAdapter.isEnabled) { - bluetoothAdapter.disable() - stateFlow.filter { it == BluetoothAdapter.STATE_OFF }.first() - } + if (bluetoothAdapter.isEnabled) { + bluetoothAdapter.disable() + stateFlow.filter { it == BluetoothAdapter.STATE_OFF }.first() + } - // TODO: b/234892968 - delay(2000L) + // TODO: b/234892968 + delay(2000L) - bluetoothAdapter.enable() - stateFlow.filter { it == BluetoothAdapter.STATE_ON }.first() + bluetoothAdapter.enable() + stateFlow.filter { it == BluetoothAdapter.STATE_ON }.first() - // The last expression is the return value. - Empty.getDefaultInstance() - } + // The last expression is the return value. + Empty.getDefaultInstance() + } .invokeOnCompletion { Log.i(TAG, "Shutdown the gRPC Server") server.shutdownNow() @@ -117,12 +116,55 @@ class Host(private val context: Context, private val server: Server) : HostImplB } } + private suspend fun waitPairingRequestIntent(address: String) { + Log.i(TAG, "waitPairingRequestIntent: address=$address") + var pairingRequestIntent = + flow + .filter { it.getAction() == BluetoothDevice.ACTION_PAIRING_REQUEST } + .filter { it.getBluetoothDeviceExtra().address == address } + .first() + + val bluetoothDevice = pairingRequestIntent.getBluetoothDeviceExtra() + val pairingVariant = + pairingRequestIntent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR) + + if ( + pairingVariant == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION || + pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT || + pairingVariant == BluetoothDevice.PAIRING_VARIANT_PIN + ) { + bluetoothDevice.setPairingConfirmation(true) + } + } + + private suspend fun waitBondIntent(address: String) { + // We only wait for bonding to be completed since we only need the ACL connection to be + // established with the peer device (on Android state connected is sent when all profiles + // have been connected). + Log.i(TAG, "waitBondIntent: address=$address") + flow + .filter { it.getAction() == BluetoothDevice.ACTION_BOND_STATE_CHANGED } + .filter { it.getBluetoothDeviceExtra().address == address } + .map { it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) } + .filter { it == BluetoothDevice.BOND_BONDED } + .first() + } + + private suspend fun waitConnectionIntent(address: String) { + val acceptPairingJob = scope.launch { waitPairingRequestIntent(address) } + waitBondIntent(address) + if (acceptPairingJob.isActive) { + acceptPairingJob.cancel() + } + } + override fun waitConnection( request: WaitConnectionRequest, responseObserver: StreamObserver ) { grpcUnary(scope, responseObserver) { - val address = MacAddress.fromBytes(request.address.toByteArray()).toString().uppercase() + val address = request.address.decodeToString() + Log.i(TAG, "waitConnection: address=$address") if (!bluetoothAdapter.isEnabled) { @@ -130,51 +172,62 @@ class Host(private val context: Context, private val server: Server) : HostImplB throw Status.UNKNOWN.asException() } - // Start a new coroutine that will accept any pairing request from the device. - val acceptPairingJob = - scope.launch { - val pairingRequestIntent = - flow - .filter { it.getAction() == BluetoothDevice.ACTION_PAIRING_REQUEST } - .filter { it.getBluetoothDeviceExtra().address == address } - .first() - - val bluetoothDevice = pairingRequestIntent.getBluetoothDeviceExtra() - val pairingVariant = - pairingRequestIntent.getIntExtra( - BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.ERROR - ) - - if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION || - pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT || - pairingVariant == BluetoothDevice.PAIRING_VARIANT_PIN - ) { - bluetoothDevice.setPairingConfirmation(true) - } - } + waitConnectionIntent(address) - // We only wait for bonding to be completed since we only need the ACL connection to be - // established with the peer device (on Android state connected is sent when all profiles - // have been connected). - flow - .filter { it.getAction() == BluetoothDevice.ACTION_BOND_STATE_CHANGED } - .filter { it.getBluetoothDeviceExtra().address == address } - .map { it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) } - .filter { it == BluetoothDevice.BOND_BONDED } - .first() + WaitConnectionResponse.newBuilder() + .setConnection(Connection.newBuilder().setCookie(ByteString.copyFromUtf8(address)).build()) + .build() + } + } + + override fun connect(request: ConnectRequest, responseObserver: StreamObserver) { + grpcUnary(scope, responseObserver) { + val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter) - // Cancel the accept pairing coroutine if still active. - if (acceptPairingJob.isActive) { - acceptPairingJob.cancel() + Log.i(TAG, "connect: address=$bluetoothDevice") + + if (!bluetoothDevice.isConnected()) { + bluetoothDevice.createBond() + waitConnectionIntent(bluetoothDevice.address) } - WaitConnectionResponse.newBuilder() - .setConnection(Connection.newBuilder().setCookie(ByteString.copyFromUtf8(address)).build()) + ConnectResponse.newBuilder() + .setConnection( + Connection.newBuilder() + .setCookie(ByteString.copyFromUtf8(bluetoothDevice.address)) + .build() + ) .build() } } + override fun deletePairing( + request: DeletePairingRequest, + responseObserver: StreamObserver + ) { + grpcUnary(scope, responseObserver) { + val device = request.address.toBluetoothDevice(bluetoothAdapter) + Log.i(TAG, "DeletePairing: device=$device") + + if (device.removeBond()) { + Log.i(TAG, "DeletePairing: device=$device - wait BOND_NONE intent") + flow + .filter { + it.getAction() == BluetoothDevice.ACTION_BOND_STATE_CHANGED && + it.getBluetoothDeviceExtra() == device && + it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) == + BluetoothDevice.BOND_NONE && + it.getIntExtra(BluetoothDevice.EXTRA_REASON, BluetoothAdapter.ERROR) == + BluetoothDevice.BOND_SUCCESS + } + .first() + } else { + Log.i(TAG, "DeletePairing: device=$device - Already unpaired") + } + DeletePairingResponse.getDefaultInstance() + } + } + override fun disconnect( request: DisconnectRequest, responseObserver: StreamObserver diff --git a/android/pandora/server/src/com/android/pandora/Server.kt b/android/pandora/server/src/com/android/pandora/Server.kt index ce44afea430..776eb6e708e 100644 --- a/android/pandora/server/src/com/android/pandora/Server.kt +++ b/android/pandora/server/src/com/android/pandora/Server.kt @@ -29,12 +29,19 @@ class Server(context: Context) { private var host: Host private var a2dp: A2dp + private var hfp: Hfp private var grpcServer: GrpcServer init { host = Host(context, this) a2dp = A2dp(context) - grpcServer = NettyServerBuilder.forPort(GRPC_PORT).addService(host).addService(a2dp).build() + hfp = Hfp(context) + grpcServer = + NettyServerBuilder.forPort(GRPC_PORT) + .addService(host) + .addService(a2dp) + .addService(hfp) + .build() Log.d(TAG, "Starting Pandora Server") grpcServer.start() @@ -44,6 +51,7 @@ class Server(context: Context) { fun shutdownNow() { host.deinit() a2dp.deinit() + hfp.deinit() grpcServer.shutdownNow() } diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt index 7423e871959..4a6fad87eeb 100644 --- a/android/pandora/server/src/com/android/pandora/Utils.kt +++ b/android/pandora/server/src/com/android/pandora/Utils.kt @@ -16,6 +16,7 @@ package com.android.pandora +import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile @@ -23,6 +24,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.net.MacAddress +import com.google.protobuf.ByteString import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -33,6 +36,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull /** * Creates a cold flow of intents based on an intent filter. If used multiple times in a same class, @@ -112,9 +116,9 @@ fun grpcUnary( @Suppress("UNCHECKED_CAST") @kotlinx.coroutines.ExperimentalCoroutinesApi fun getProfileProxy(context: Context, profile: Int): T { - var proxy: T + var proxy: BluetoothProfile? runBlocking { - val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! val bluetoothAdapter = bluetoothManager.adapter val flow = callbackFlow { @@ -130,10 +134,20 @@ fun getProfileProxy(context: Context, profile: Int): T { awaitClose {} } - proxy = flow.first() as T + proxy = withTimeoutOrNull(5_000) { flow.first() } } - return proxy + return proxy!! as T } fun Intent.getBluetoothDeviceExtra(): BluetoothDevice = this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) + +fun ByteString.decodeToString(): String = + MacAddress.fromBytes(this.toByteArray()).toString().uppercase() + +fun ByteString.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice = + adapter.getRemoteDevice(this.decodeToString()) + +fun String.toByteArray(): ByteArray = MacAddress.fromString(this).toByteArray() + +fun BluetoothDevice.toByteArray(): ByteArray = this.address.toByteArray() diff --git a/system/bta/ag/bta_ag_cmd.cc b/system/bta/ag/bta_ag_cmd.cc index 7d0622640b4..89783a0acca 100644 --- a/system/bta/ag/bta_ag_cmd.cc +++ b/system/bta/ag/bta_ag_cmd.cc @@ -763,7 +763,7 @@ static void bta_ag_bind_response(tBTA_AG_SCB* p_scb, uint8_t arg_type) { bta_ag_send_ok(p_scb); - /* If the service level connection wan't already open, now it's open */ + /* If the service level connection wasn't already open, now it's open */ if (!p_scb->svc_conn) { bta_ag_svc_conn_open(p_scb, tBTA_AG_DATA::kEmpty); } -- GitLab From 60cc89b1ffb9e6a1220f073b07288cc6b8248a5d Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Mon, 18 Jul 2022 11:53:48 -0700 Subject: [PATCH 036/998] Escape MMIs that are not Python identifiers Map 6000 -> _mmi_6000 Bug: 239453735 Bug: 241962982 Test: atest pts-bot:SDP/SR/SA/BV-20-C -v Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I89181fa2a082b000a9b210cbfb4f5136999706b6 Change-Id: I89181fa2a082b000a9b210cbfb4f5136999706b6 --- android/pandora/mmi2grpc/mmi2grpc/_proxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/pandora/mmi2grpc/mmi2grpc/_proxy.py b/android/pandora/mmi2grpc/mmi2grpc/_proxy.py index 8eb4bd80f9b..45f94965f05 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/_proxy.py +++ b/android/pandora/mmi2grpc/mmi2grpc/_proxy.py @@ -35,6 +35,8 @@ class ProfileProxy: AttributeError: the MMI is not implemented. """ try: + if not mmi_name.isidentifier(): + mmi_name = "_mmi_" + mmi_name return getattr(self, mmi_name)( test=test, description=mmi_description, pts_addr=pts_addr) except AttributeError: -- GitLab From 5251ed63b733c0d098ecd7ff9fb7bbeb85ed915f Mon Sep 17 00:00:00 2001 From: William Escande Date: Wed, 20 Jul 2022 14:52:56 -0700 Subject: [PATCH 037/998] [PANDORA] Remove Shadowing since we now can call hidden Bug: 241962982 Test: atest pts-bot Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: If3688a043247b7a0ac6c5073818c573b4944b6a3 Change-Id: If3688a043247b7a0ac6c5073818c573b4944b6a3 --- android/pandora/server/src/com/android/pandora/A2dp.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt index ca7af55c67e..b412f07965f 100644 --- a/android/pandora/server/src/com/android/pandora/A2dp.kt +++ b/android/pandora/server/src/com/android/pandora/A2dp.kt @@ -338,11 +338,4 @@ class A2dp(val context: Context) : A2DPImplBase() { } } - // TODO: Remove reflection and import framework bluetooth library when it will be available - // on AOSP. - fun BluetoothA2dp.connect(device: BluetoothDevice) = - this.javaClass.getMethod("connect", BluetoothDevice::class.java).invoke(this, device) - - fun BluetoothA2dp.disconnect(device: BluetoothDevice) = - this.javaClass.getMethod("disconnect", BluetoothDevice::class.java).invoke(this, device) } -- GitLab From d8a5c9835c93f4a569ed020515c25045eb85540f Mon Sep 17 00:00:00 2001 From: William Escande Date: Wed, 13 Jul 2022 23:01:55 -0700 Subject: [PATCH 038/998] [PANDORA_TEST] pts-bot HFP Coverage: HFI & SLC Create the HFP layer inside pts-bot. Add the following HFP test: * HFP/AG/HFI/BI-03-I * HFP/AG/HFI/BV-02-I * HFP/AG/SLC/BV-09-I * HFP/AG/SLC/BV-10-I Bug: 237447510 Bug: 241962982 Test: atest pts-bot:HFP/AG/HFI Test: atest pts-bot:HFP/AG/SLC Test: atest pts-bot Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I410b9d3a9b73ba49c7743dac7fd3db0aaf31da93 Change-Id: I410b9d3a9b73ba49c7743dac7fd3db0aaf31da93 --- android/pandora/mmi2grpc/mmi2grpc/hfp.py | 16 +++++++++++++- android/pandora/server/configs/PtsBotTest.xml | 2 ++ .../server/configs/pts_bot_tests_config.json | 16 ++++++++++++-- .../pandora/server/proto/pandora/hfp.proto | 19 ++++++++++------ .../server/src/com/android/pandora/Hfp.kt | 22 +++++++++++++------ .../server/src/com/android/pandora/Utils.kt | 4 ++++ 6 files changed, 62 insertions(+), 17 deletions(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/hfp.py b/android/pandora/mmi2grpc/mmi2grpc/hfp.py index c05b2caaec3..93c902af507 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/hfp.py +++ b/android/pandora/mmi2grpc/mmi2grpc/hfp.py @@ -30,6 +30,8 @@ class HFPProxy(ProfileProxy): self.hfp = HFP(channel) self.host = Host(channel) + self.connection = None + @assert_description def TSC_delete_pairing_iut(self, pts_addr: bytes, **kwargs): """ @@ -40,6 +42,18 @@ class HFPProxy(ProfileProxy): self.host.DeletePairing(address=pts_addr) return "OK" + @assert_description + def TSC_iut_enable_slc(self, pts_addr: bytes, **kwargs): + """ + Click Ok, then initiate a service level connection from the + Implementation Under Test (IUT) to the PTS. + """ + + if not self.connection: + self.connection = self.host.Connect(address=pts_addr).connection + self.hfp.EnableSlc(connection=self.connection) + return "OK" + @assert_description def TSC_iut_search(self, **kwargs): """ @@ -66,5 +80,5 @@ class HFPProxy(ProfileProxy): Implementation Under Test (IUT). """ - self.hfp.DisableSlc(address=pts_addr) + self.hfp.DisableSlc(connection=self.connection) return "OK" diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index 69ca6c4854a..6779141345e 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -23,5 +23,7 @@ diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index bc0b82e6a65..f52fed4c680 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -40,7 +40,12 @@ "AVDTP/SRC/INT/SIG/SMG/BV-28-C", "AVDTP/SRC/INT/SIG/SMG/BV-31-C", "AVDTP/SRC/INT/SIG/SYN/BV-05-C", - "AVDTP/SRC/INT/TRA/BTR/BV-01-C" + "AVDTP/SRC/INT/TRA/BTR/BV-01-C", + "HFP/AG/DIS/BV-01-I", + "HFP/AG/HFI/BI-03-I", + "HFP/AG/HFI/BV-02-I", + "HFP/AG/SLC/BV-09-I", + "HFP/AG/SLC/BV-10-I" ], "skip": [ "A2DP/SRC/AS/BV-01-I", @@ -61,7 +66,14 @@ "AVDTP/SRC/INT/SIG/SMG/BV-11-C", "AVDTP/SRC/INT/SIG/SMG/BV-13-C", "AVDTP/SRC/INT/SIG/SMG/BV-23-C", - "AVDTP/SRC/INT/SIG/SMG/ESR05/BV-13-C" + "AVDTP/SRC/INT/SIG/SMG/ESR05/BV-13-C", + "HFP/AG/SLC/BV-01-C", + "HFP/AG/SLC/BV-02-C", + "HFP/AG/SLC/BV-03-C", + "HFP/AG/SLC/BV-04-C", + "HFP/AG/SLC/BV-05-I", + "HFP/AG/SLC/BV-06-I", + "HFP/AG/SLC/BV-07-I" ], "ics": { "TSPC_4.0HCI_1a_2": true, diff --git a/android/pandora/server/proto/pandora/hfp.proto b/android/pandora/server/proto/pandora/hfp.proto index 3ee86c0aa59..6500e7cece1 100644 --- a/android/pandora/server/proto/pandora/hfp.proto +++ b/android/pandora/server/proto/pandora/hfp.proto @@ -5,19 +5,24 @@ option java_outer_classname = "HfpProto"; package pandora; import "pandora/host.proto"; -import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; // Service to trigger HFP (Hands Free Profile) procedures. service HFP { + // Enable Service level connection + rpc EnableSlc(EnableSlcRequest) returns (google.protobuf.Empty); // Disable Service level connection - rpc DisableSlc(DisableSlcRequest) returns (DisableSlcResponse); + rpc DisableSlc(DisableSlcRequest) returns (google.protobuf.Empty); +} + +// Request of the `EnableSlc` method. +message EnableSlcRequest { + // Connection crafted by grpc server + Connection connection = 1; } // Request of the `DisableSlc` method. message DisableSlcRequest { - // Local Bluetooth Device Address as array of 6 bytes. - bytes address = 1; + // Connection crafted by grpc server + Connection connection = 1; } - -// Response of the `DisableSlc` method. -message DisableSlcResponse {} diff --git a/android/pandora/server/src/com/android/pandora/Hfp.kt b/android/pandora/server/src/com/android/pandora/Hfp.kt index b10310e2aa1..3f89130d685 100644 --- a/android/pandora/server/src/com/android/pandora/Hfp.kt +++ b/android/pandora/server/src/com/android/pandora/Hfp.kt @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent import android.content.IntentFilter +import com.google.protobuf.Empty import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,16 +57,23 @@ class Hfp(val context: Context) : HFPImplBase() { scope.cancel() } - override fun disableSlc( - request: DisableSlcRequest, - responseObserver: StreamObserver - ) { - grpcUnary(scope, responseObserver) { - val device = request.address.toBluetoothDevice(bluetoothAdapter) + override fun enableSlc(request: EnableSlcRequest, responseObserver: StreamObserver) { + grpcUnary(scope, responseObserver) { + val device = request.connection.toBluetoothDevice(bluetoothAdapter) + + bluetoothHfp.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED) + + Empty.getDefaultInstance() + } + } + + override fun disableSlc(request: DisableSlcRequest, responseObserver: StreamObserver) { + grpcUnary(scope, responseObserver) { + val device = request.connection.toBluetoothDevice(bluetoothAdapter) bluetoothHfp.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) - DisableSlcResponse.getDefaultInstance() + Empty.getDefaultInstance() } } } diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt index 4a6fad87eeb..bfbc0d6e35a 100644 --- a/android/pandora/server/src/com/android/pandora/Utils.kt +++ b/android/pandora/server/src/com/android/pandora/Utils.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull +import pandora.HostProto.Connection /** * Creates a cold flow of intents based on an intent filter. If used multiple times in a same class, @@ -148,6 +149,9 @@ fun ByteString.decodeToString(): String = fun ByteString.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice = adapter.getRemoteDevice(this.decodeToString()) +fun Connection.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice = + adapter.getRemoteDevice(this.cookie.toByteArray().decodeToString()) + fun String.toByteArray(): ByteArray = MacAddress.fromString(this).toByteArray() fun BluetoothDevice.toByteArray(): ByteArray = this.address.toByteArray() -- GitLab From b7eddd0d17198c5a0a783017c0c5df291d0210fc Mon Sep 17 00:00:00 2001 From: William Escande Date: Thu, 21 Jul 2022 14:32:49 -0700 Subject: [PATCH 039/998] Add a pre-test mmi to get the pts address Some HFP test need to start waiting for a connection BEFORE the test start and we need the address to wait for it Bug: 237447510 Bug: 241962982 Test: atest pts-bot Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I3df66abb2c5a99f14459beee93e7784fdc6caf19 Change-Id: I3df66abb2c5a99f14459beee93e7784fdc6caf19 --- android/pandora/mmi2grpc/mmi2grpc/_proxy.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/_proxy.py b/android/pandora/mmi2grpc/mmi2grpc/_proxy.py index 45f94965f05..1e7718af8c9 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/_proxy.py +++ b/android/pandora/mmi2grpc/mmi2grpc/_proxy.py @@ -11,18 +11,18 @@ # 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. - """Profile proxy base module.""" from mmi2grpc._helpers import format_function +from mmi2grpc._helpers import assert_description + +import sys class ProfileProxy: """Profile proxy base class.""" - def interact( - self, test: str, mmi_name: str, mmi_description: str, - pts_addr: bytes): + def interact(self, test: str, mmi_name: str, mmi_description: str, pts_addr: bytes): """Translate a MMI call to its corresponding implementation. Args: @@ -37,8 +37,10 @@ class ProfileProxy: try: if not mmi_name.isidentifier(): mmi_name = "_mmi_" + mmi_name - return getattr(self, mmi_name)( - test=test, description=mmi_description, pts_addr=pts_addr) + return getattr(self, mmi_name)(test=test, description=mmi_description, pts_addr=pts_addr) except AttributeError: code = format_function(mmi_name, mmi_description) assert False, f'Unhandled mmi {id}\n{code}' + + def test_started(self, test: str, description: str, pts_addr: bytes): + return "OK" -- GitLab From fe426c1605c10a809284b11db1a2b5d1d6818fac Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Mon, 18 Jul 2022 13:40:43 -0700 Subject: [PATCH 040/998] Pandora: Add SDP/SR/ support Test: atest pts-bot:SDP/SR -v Bug: 239469107 Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I75606064c67dac1406547db459b4fa17a78efd5e Change-Id: I75606064c67dac1406547db459b4fa17a78efd5e --- android/pandora/mmi2grpc/mmi2grpc/__init__.py | 8 ++ android/pandora/mmi2grpc/mmi2grpc/sdp.py | 105 ++++++++++++++++++ android/pandora/server/configs/PtsBotTest.xml | 1 + 3 files changed, 114 insertions(+) create mode 100644 android/pandora/mmi2grpc/mmi2grpc/sdp.py diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py index 84bd6af39da..c5de1e1fea3 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py +++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py @@ -23,6 +23,7 @@ import grpc from mmi2grpc.a2dp import A2DPProxy from mmi2grpc.hfp import HFPProxy +from mmi2grpc.sdp import SDPProxy from mmi2grpc._helpers import format_proxy from pandora.host_grpc import Host @@ -52,6 +53,7 @@ class IUT: # Profile proxies. self._a2dp = None self._hfp = None + self._sdp = None def __enter__(self): """Resets the IUT when starting a PTS test.""" @@ -63,6 +65,7 @@ class IUT: def __exit__(self, exc_type, exc_value, exc_traceback): self._a2dp = None self._hfp = None + self._sdp = None def _retry(self, func): @@ -111,6 +114,11 @@ class IUT: if not self._hfp: self._hfp = HFPProxy(grpc.insecure_channel(f'localhost:{self.port}')) return self._hfp.interact(test, interaction, description, pts_address) + # Handles SDP MMIs. + if profile in ('SDP'): + if not self._sdp: + self._sdp = SDPProxy(grpc.insecure_channel(f'localhost:{self.port}')) + return self._sdp.interact(test, interaction, description, pts_address) # Handles unsupported profiles. code = format_proxy(profile, interaction, description) diff --git a/android/pandora/mmi2grpc/mmi2grpc/sdp.py b/android/pandora/mmi2grpc/mmi2grpc/sdp.py new file mode 100644 index 00000000000..a244aac5531 --- /dev/null +++ b/android/pandora/mmi2grpc/mmi2grpc/sdp.py @@ -0,0 +1,105 @@ +# Copyright 2022 Google LLC +# +# 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 +# +# https://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. +"""SDP proxy module.""" + +from mmi2grpc._helpers import assert_description +from mmi2grpc._proxy import ProfileProxy + +import sys +import threading +import os +import socket + + +class SDPProxy(ProfileProxy): + + def __init__(self, channel: str): + super().__init__() + + @assert_description + def _mmi_6000(self, **kwargs): + """ + If necessary take action to accept the SDP channel connection. + """ + + return "OK" + + @assert_description + def _mmi_6001(self, **kwargs): + """ + If necessary take action to respond to the Service Attribute operation + appropriately. + """ + + return "OK" + + @assert_description + def _mmi_6002(self, **kwargs): + """ + If necessary take action to accept the Service Search operation. + """ + + return "OK" + + @assert_description + def _mmi_6003(self, **kwargs): + """ + If necessary take action to respond to the Service Search Attribute + operation appropriately. + """ + + return "OK" + + @assert_description + def TSC_SDP_mmi_verify_browsable_services(self, **kwargs): + """ + Are all browsable service classes listed below? + + 0x1800, 0x110A, 0x110C, + 0x110E, 0x1112, 0x1203, 0x111F, 0x1203, 0x1855, 0x1132, 0x1116, 0x1115, + 0x112F, 0x1105 + """ + """ + This is the decoded list of UUIDs: + Service Classes and Profiles 0x1105 OBEXObjectPush + Service Classes and Profiles 0x110A AudioSource + Service Classes and Profiles 0x110C A/V_RemoteControlTarget + Service Classes and Profiles 0x110E A/V_RemoteControl + Service Classes and Profiles 0x1112 Headset - Audio Gateway + Service Classes and Profiles 0x1115 PANU + Service Classes and Profiles 0x1116 NAP + Service Classes and Profiles 0x111F HandsfreeAudioGateway + Service Classes and Profiles 0x112F Phonebook Access - PSE + Service Classes and Profiles 0x1132 Message Access Server + Service Classes and Profiles 0x1203 GenericAudio + GATT Service 0x1800 Generic Access + GATT Service 0x1855 TMAS + + The Android API only returns a subset of the profiles: + 0x110A, 0x1112, 0x111F, 0x112F, 0x1132, + + Since the API doesn't return the full set, this test uses the + description to check that the number of profiles does not change + from the last time the test was successfully run. + + Adding or Removing services from Android will cause this + test to be fail. Updating the description above will cause + it to pass again. + + The other option is to add a call to btif_enable_service() for each + profile which is browsable in SDP. Then you can add a Host GRPC call + to BluetoothAdapter.getUuidsList and match the returned UUIDs to the + list given by PTS. + """ + return "OK" diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index 6779141345e..76332d47238 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -25,5 +25,6 @@

{ + let mut queue = self.lmp.borrow_mut(); + let packet = queue.front().and_then(|packet| packet.clone().try_into().ok()); + + if let Some(packet) = packet { + queue.pop_front(); + Poll::Ready(packet) + } else { + Poll::Pending + } + } + + fn reset(&self) { + self.peer.set(hci::EMPTY_ADDRESS); + self.hci.set(None); + self.lmp.borrow_mut().clear(); + } +} + +#[derive(Error, Debug)] +pub enum LinkManagerError { + #[error("Unknown peer")] + UnknownPeer, + #[error("Unhandled HCI packet")] + UnhandledHciPacket, + #[error("Maximum number of links reached")] + MaxNumberOfLink, +} + +/// Max number of Bluetooth Peers +pub const MAX_PEER_NUMBER: usize = 7; + +pub struct LinkManager { + ops: LinkManagerOps, + links: [Link; MAX_PEER_NUMBER], + procedures: RefCell<[Option>>>; MAX_PEER_NUMBER]>, +} + +impl LinkManager { + pub fn new(ops: LinkManagerOps) -> Self { + Self { ops, links: Default::default(), procedures: Default::default() } + } + + fn get_link(&self, peer: hci::Address) -> Option<&Link> { + self.links.iter().find(|link| link.peer.get() == peer) + } + + pub fn ingest_lmp( + &self, + from: hci::Address, + packet: lmp::PacketPacket, + ) -> Result<(), LinkManagerError> { + let link = self.get_link(from).ok_or(LinkManagerError::UnknownPeer)?; + link.ingest_lmp(packet); + Ok(()) + } + + pub fn ingest_hci(&self, command: hci::CommandPacket) -> Result<(), LinkManagerError> { + // Try to find the peer address from the command arguments + let peer = hci::command_connection_handle(&command) + .map(|handle| self.ops.get_address(handle)) + .or_else(|| hci::command_remote_device_address(&command)); + + if let Some(peer) = peer { + let link = self.get_link(peer).ok_or(LinkManagerError::UnknownPeer)?; + link.ingest_hci(command); + Ok(()) + } else { + Err(LinkManagerError::UnhandledHciPacket) + } + } + + pub fn add_link(self: &Rc, peer: hci::Address) -> Result<(), LinkManagerError> { + let index = self.links.iter().position(|link| link.peer.get().is_empty()); + + if let Some(index) = index { + self.links[index].peer.set(peer); + let context = LinkContext { index: index as u8, manager: Rc::downgrade(self) }; + self.procedures.borrow_mut()[index] = Some(Box::pin(procedure::run(context))); + Ok(()) + } else { + Err(LinkManagerError::UnhandledHciPacket) + } + } + + pub fn remove_link(&self, peer: hci::Address) -> Result<(), LinkManagerError> { + let index = self.links.iter().position(|link| link.peer.get() == peer); + + if let Some(index) = index { + self.links[index].reset(); + self.procedures.borrow_mut()[index] = None; + Ok(()) + } else { + Err(LinkManagerError::UnknownPeer) + } + } + + pub fn tick(&self) { + let waker = noop_waker(); + + for procedures in self.procedures.borrow_mut().iter_mut().filter_map(Option::as_mut) { + let _ = procedures.as_mut().poll(&mut Context::from_waker(&waker)); + } + } + + fn link(&self, idx: u8) -> &Link { + &self.links[idx as usize] + } +} + +struct LinkContext { + index: u8, + manager: Weak, +} + +impl procedure::Context for LinkContext { + fn poll_hci_command>(&self) -> Poll { + if let Some(manager) = self.manager.upgrade() { + manager.link(self.index).poll_hci_command() + } else { + Poll::Pending + } + } + + fn poll_lmp_packet>(&self) -> Poll

{ + if let Some(manager) = self.manager.upgrade() { + manager.link(self.index).poll_lmp_packet() + } else { + Poll::Pending + } + } + + fn send_hci_event>(&self, event: E) { + if let Some(manager) = self.manager.upgrade() { + manager.ops.send_hci_event(&*event.into().to_vec()) + } + } + + fn send_lmp_packet>(&self, packet: P) { + if let Some(manager) = self.manager.upgrade() { + manager.ops.send_lmp_packet(self.peer_address(), &*packet.into().to_vec()) + } + } + + fn peer_address(&self) -> hci::Address { + if let Some(manager) = self.manager.upgrade() { + manager.link(self.index).peer.get() + } else { + hci::EMPTY_ADDRESS + } + } + + fn peer_handle(&self) -> u16 { + if let Some(manager) = self.manager.upgrade() { + manager.ops.get_handle(self.peer_address()) + } else { + 0 + } + } + + fn extended_features(&self, features_page: u8) -> u64 { + if let Some(manager) = self.manager.upgrade() { + manager.ops.extended_features(features_page) + } else { + 0 + } + } +} diff --git a/tools/rootcanal/lmp/src/packets.rs b/tools/rootcanal/lmp/src/packets.rs new file mode 100644 index 00000000000..250d2dd4667 --- /dev/null +++ b/tools/rootcanal/lmp/src/packets.rs @@ -0,0 +1,58 @@ +pub mod hci { + pub use bt_packets::custom_types::*; + pub use bt_packets::hci::*; + + pub fn command_remote_device_address(command: &CommandPacket) -> Option

{ + #[allow(unused_imports)] + use Option::None; + use SecurityCommandChild::*; // Overwrite `None` variant of `Child` enum + + match command.specialize() { + CommandChild::SecurityCommand(command) => match command.specialize() { + LinkKeyRequestReply(packet) => Some(packet.get_bd_addr()), + LinkKeyRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + PinCodeRequestReply(packet) => Some(packet.get_bd_addr()), + PinCodeRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + IoCapabilityRequestReply(packet) => Some(packet.get_bd_addr()), + IoCapabilityRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + UserConfirmationRequestReply(packet) => Some(packet.get_bd_addr()), + UserConfirmationRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + UserPasskeyRequestReply(packet) => Some(packet.get_bd_addr()), + UserPasskeyRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + RemoteOobDataRequestReply(packet) => Some(packet.get_bd_addr()), + RemoteOobDataRequestNegativeReply(packet) => Some(packet.get_bd_addr()), + SendKeypressNotification(packet) => Some(packet.get_bd_addr()), + _ => None, + }, + _ => None, + } + } + + pub fn command_connection_handle(command: &CommandPacket) -> Option { + use ConnectionManagementCommandChild::*; + #[allow(unused_imports)] + use Option::None; // Overwrite `None` variant of `Child` enum + + match command.specialize() { + CommandChild::AclCommand(command) => match command.specialize() { + AclCommandChild::ConnectionManagementCommand(command) => { + match command.specialize() { + AuthenticationRequested(packet) => Some(packet.get_connection_handle()), + SetConnectionEncryption(packet) => Some(packet.get_connection_handle()), + _ => None, + } + } + _ => None, + }, + _ => None, + } + } +} + +pub mod lmp { + #![allow(clippy::all)] + #![allow(unused)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/lmp_packets.rs")); +} diff --git a/tools/rootcanal/lmp/src/procedure/authentication.rs b/tools/rootcanal/lmp/src/procedure/authentication.rs new file mode 100644 index 00000000000..8436de5c5a4 --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/authentication.rs @@ -0,0 +1,162 @@ +// Bluetooth Core, Vol 2, Part C, 4.2.1 + +use num_traits::ToPrimitive; + +use crate::either::Either; +use crate::num_hci_command_packets; +use crate::packets::{hci, lmp}; +use crate::procedure::features; +use crate::procedure::legacy_pairing; +use crate::procedure::secure_simple_pairing; +use crate::procedure::Context; + +async fn secure_simple_pairing_supported(ctx: &impl Context) -> bool { + let ssp_bit = hci::LMPFeaturesPage1Bits::SecureSimplePairingHostSupport.to_u64().unwrap(); + let local_supported = ctx.extended_features(1) & ssp_bit != 0; + // Lazy peer features + let peer_supported = async move { + let page = if let Some(page) = ctx.peer_extended_features(1) { + page + } else { + features::initiate(ctx, 1).await + }; + page & ssp_bit != 0 + }; + local_supported && peer_supported.await +} + +pub async fn send_authentication_challenge(ctx: &impl Context, transaction_id: u8) { + ctx.send_lmp_packet(lmp::AuRandBuilder { transaction_id, random_number: [0; 16] }.build()); + let _ = ctx.receive_lmp_packet::().await; +} + +pub async fn initiate(ctx: &impl Context) { + let _ = ctx.receive_hci_command::().await; + ctx.send_hci_event( + hci::AuthenticationRequestedStatusBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + } + .build(), + ); + + ctx.send_hci_event(hci::LinkKeyRequestBuilder { bd_addr: ctx.peer_address() }.build()); + + let pairing = match ctx.receive_hci_command::>().await { + Either::Left(_reply) => { + ctx.send_hci_event( + hci::LinkKeyRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + false + }, + Either::Right(_) => { + ctx.send_hci_event( + hci::LinkKeyRequestNegativeReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + let result = if secure_simple_pairing_supported(ctx).await { + secure_simple_pairing::initiate(ctx).await + } else { + legacy_pairing::initiate(ctx).await + }; + + if result.is_err() { + ctx.send_hci_event( + hci::AuthenticationCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + connection_handle: ctx.peer_handle(), + } + .build(), + ); + return; + } + true + } + }; + + send_authentication_challenge(ctx, 0).await; + + // Link Key Calculation + if pairing { + let _random_number = ctx.receive_lmp_packet::().await; + + // TODO: Resolve authentication challenge + ctx.send_lmp_packet( + lmp::SresBuilder { transaction_id: 0, authentication_rsp: [0; 4] }.build(), + ); + + ctx.send_hci_event( + hci::LinkKeyNotificationBuilder { + bd_addr: ctx.peer_address(), + key_type: hci::KeyType::AuthenticatedP192, + link_key: [0; 16], + } + .build(), + ); + } + + ctx.send_hci_event( + hci::AuthenticationCompleteBuilder { + status: hci::ErrorCode::Success, + connection_handle: ctx.peer_handle(), + } + .build(), + ); +} + +pub async fn respond(ctx: &impl Context) { + match ctx.receive_lmp_packet:: + >>() + .await + { + Either::Left(_random_number) => { + // TODO: Resolve authentication challenge + // TODO: Ask for link key + ctx.send_lmp_packet(lmp::SresBuilder { transaction_id: 0, authentication_rsp: [0; 4] }.build()); + }, + Either::Right(pairing) => { + let result = match pairing { + Either::Left(io_capability_request) => + secure_simple_pairing::respond(ctx, io_capability_request).await, + Either::Right(in_rand) => + legacy_pairing::respond(ctx, in_rand).await, + }; + + if result.is_err() { + return; + } + + // Link Key Calculation + + let _random_number = ctx.receive_lmp_packet::().await; + // TODO: Resolve authentication challenge + ctx.send_lmp_packet(lmp::SresBuilder { transaction_id: 0, authentication_rsp: [0; 4] }.build()); + + send_authentication_challenge(ctx, 0).await; + + ctx.send_hci_event( + hci::LinkKeyNotificationBuilder { + bd_addr: ctx.peer_address(), + key_type: hci::KeyType::AuthenticatedP192, + link_key: [0; 16], + } + .build(), + ); + } + } +} diff --git a/tools/rootcanal/lmp/src/procedure/encryption.rs b/tools/rootcanal/lmp/src/procedure/encryption.rs new file mode 100644 index 00000000000..df44124e76a --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/encryption.rs @@ -0,0 +1,83 @@ +// Bluetooth Core, Vol 2, Part C, 4.2.5 + +use crate::num_hci_command_packets; +use crate::packets::{hci, lmp}; +use crate::procedure::Context; + +pub async fn initiate(ctx: &impl Context) { + // TODO: handle turn off + let _ = ctx.receive_hci_command::().await; + ctx.send_hci_event( + hci::SetConnectionEncryptionStatusBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + } + .build(), + ); + + // TODO: handle failure + let _ = ctx + .send_accepted_lmp_packet( + lmp::EncryptionModeReqBuilder { transaction_id: 0, encryption_mode: 0x1 }.build(), + ) + .await; + + // TODO: handle failure + let _ = ctx + .send_accepted_lmp_packet( + lmp::EncryptionKeySizeReqBuilder { transaction_id: 0, key_size: 16 }.build(), + ) + .await; + + // TODO: handle failure + let _ = ctx + .send_accepted_lmp_packet( + lmp::StartEncryptionReqBuilder { transaction_id: 0, random_number: [0; 16] }.build(), + ) + .await; + + ctx.send_hci_event( + hci::EncryptionChangeBuilder { + status: hci::ErrorCode::Success, + connection_handle: ctx.peer_handle(), + encryption_enabled: hci::EncryptionEnabled::On, + } + .build(), + ); +} + +pub async fn respond(ctx: &impl Context) { + // TODO: handle + let _ = ctx.receive_lmp_packet::().await; + ctx.send_lmp_packet( + lmp::AcceptedBuilder { transaction_id: 0, accepted_opcode: lmp::Opcode::EncryptionModeReq } + .build(), + ); + + let _ = ctx.receive_lmp_packet::().await; + ctx.send_lmp_packet( + lmp::AcceptedBuilder { + transaction_id: 0, + accepted_opcode: lmp::Opcode::EncryptionKeySizeReq, + } + .build(), + ); + + let _ = ctx.receive_lmp_packet::().await; + ctx.send_lmp_packet( + lmp::AcceptedBuilder { + transaction_id: 0, + accepted_opcode: lmp::Opcode::StartEncryptionReq, + } + .build(), + ); + + ctx.send_hci_event( + hci::EncryptionChangeBuilder { + status: hci::ErrorCode::Success, + connection_handle: ctx.peer_handle(), + encryption_enabled: hci::EncryptionEnabled::On, + } + .build(), + ); +} diff --git a/tools/rootcanal/lmp/src/procedure/features.rs b/tools/rootcanal/lmp/src/procedure/features.rs new file mode 100644 index 00000000000..ced80b52553 --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/features.rs @@ -0,0 +1,35 @@ +// Bluetooth Core, Vol 2, Part C, 4.3.4 + +use crate::packets::lmp; +use crate::procedure::Context; + +pub async fn initiate(ctx: &impl Context, features_page: u8) -> u64 { + ctx.send_lmp_packet( + lmp::FeaturesReqExtBuilder { + transaction_id: 0, + features_page, + max_supported_page: 1, + extended_features: ctx.extended_features(features_page).to_le_bytes(), + } + .build(), + ); + + u64::from_le_bytes( + *ctx.receive_lmp_packet::().await.get_extended_features(), + ) +} + +pub async fn respond(ctx: &impl Context) { + let req = ctx.receive_lmp_packet::().await; + let features_page = req.get_features_page(); + + ctx.send_lmp_packet( + lmp::FeaturesResExtBuilder { + transaction_id: 0, + features_page, + max_supported_page: 1, + extended_features: ctx.extended_features(features_page).to_le_bytes(), + } + .build(), + ); +} diff --git a/tools/rootcanal/lmp/src/procedure/legacy_pairing.rs b/tools/rootcanal/lmp/src/procedure/legacy_pairing.rs new file mode 100644 index 00000000000..4e637a906be --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/legacy_pairing.rs @@ -0,0 +1,59 @@ +// Bluetooth Core, Vol 2, Part C, 4.2.2 + +use crate::packets::{hci, lmp}; +use crate::procedure::Context; + +use crate::num_hci_command_packets; + +pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { + ctx.send_hci_event(hci::PinCodeRequestBuilder { bd_addr: ctx.peer_address() }.build()); + + let _pin_code = ctx.receive_hci_command::().await; + + ctx.send_hci_event( + hci::PinCodeRequestReplyCompleteBuilder { + num_hci_command_packets: 1, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + // TODO: handle result + let _ = ctx + .send_accepted_lmp_packet( + lmp::InRandBuilder { transaction_id: 0, random_number: [0; 16] }.build(), + ) + .await; + + ctx.send_lmp_packet(lmp::CombKeyBuilder { transaction_id: 0, random_number: [0; 16] }.build()); + + let _ = ctx.receive_lmp_packet::().await; + + Ok(()) +} + +pub async fn respond(ctx: &impl Context, _request: lmp::InRandPacket) -> Result<(), ()> { + ctx.send_hci_event(hci::PinCodeRequestBuilder { bd_addr: ctx.peer_address() }.build()); + + let _pin_code = ctx.receive_hci_command::().await; + + ctx.send_hci_event( + hci::PinCodeRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + ctx.send_lmp_packet( + lmp::AcceptedBuilder { transaction_id: 0, accepted_opcode: lmp::Opcode::InRand }.build(), + ); + + let _ = ctx.receive_lmp_packet::().await; + + ctx.send_lmp_packet(lmp::CombKeyBuilder { transaction_id: 0, random_number: [0; 16] }.build()); + + Ok(()) +} diff --git a/tools/rootcanal/lmp/src/procedure/mod.rs b/tools/rootcanal/lmp/src/procedure/mod.rs new file mode 100644 index 00000000000..5340840cae2 --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/mod.rs @@ -0,0 +1,134 @@ +use std::convert::TryFrom; +use std::future::Future; +use std::pin::Pin; +use std::task::{self, Poll}; + +use crate::packets::{hci, lmp}; + +pub trait Context { + fn poll_hci_command>(&self) -> Poll; + fn poll_lmp_packet>(&self) -> Poll

; + + fn send_hci_event>(&self, event: E); + fn send_lmp_packet>(&self, packet: P); + + fn peer_address(&self) -> hci::Address; + fn peer_handle(&self) -> u16; + + fn peer_extended_features(&self, _features_page: u8) -> Option { + None + } + + fn extended_features(&self, features_page: u8) -> u64; + + fn receive_hci_command>(&self) -> ReceiveFuture<'_, Self, C> { + ReceiveFuture(Self::poll_hci_command, self) + } + + fn receive_lmp_packet>(&self) -> ReceiveFuture<'_, Self, P> { + ReceiveFuture(Self::poll_lmp_packet, self) + } + + fn send_accepted_lmp_packet>( + &self, + packet: P, + ) -> SendAcceptedLmpPacketFuture<'_, Self> { + let packet = packet.into(); + let opcode = packet.get_opcode(); + self.send_lmp_packet(packet); + + SendAcceptedLmpPacketFuture(self, opcode) + } +} + +/// Future for Context::receive_hci_command and Context::receive_lmp_packet +pub struct ReceiveFuture<'a, C: ?Sized, P>(fn(&'a C) -> Poll

, &'a C); + +impl<'a, C, O> Future for ReceiveFuture<'a, C, O> +where + C: Context, +{ + type Output = O; + + fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll { + (self.0)(self.1) + } +} + +/// Future for Context::receive_hci_command and Context::receive_lmp_packet +pub struct SendAcceptedLmpPacketFuture<'a, C: ?Sized>(&'a C, lmp::Opcode); + +impl<'a, C> Future for SendAcceptedLmpPacketFuture<'a, C> +where + C: Context, +{ + type Output = Result<(), u8>; + + fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll { + let accepted = self.0.poll_lmp_packet::(); + if let Poll::Ready(accepted) = accepted { + if accepted.get_accepted_opcode() == self.1 { + return Poll::Ready(Ok(())); + } + } + + let not_accepted = self.0.poll_lmp_packet::(); + if let Poll::Ready(not_accepted) = not_accepted { + if not_accepted.get_not_accepted_opcode() == self.1 { + return Poll::Ready(Err(not_accepted.get_error_code())); + } + } + + Poll::Pending + } +} + +pub mod authentication; +mod encryption; +pub mod features; +pub mod legacy_pairing; +pub mod secure_simple_pairing; + +macro_rules! run_procedures { + ($( + $idx:tt { $procedure:expr } + )+) => {{ + $( + let $idx = async { loop { $procedure.await; } }; + crate::future::pin!($idx); + )+ + + use std::future::Future; + use std::pin::Pin; + use std::task::{Poll, Context}; + + #[allow(non_camel_case_types)] + struct Join<'a, $($idx),+> { + $($idx: Pin<&'a mut $idx>),+ + } + + #[allow(non_camel_case_types)] + impl<'a, $($idx: Future),+> Future for Join<'a, $($idx),+> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + $(assert!(self.$idx.as_mut().poll(cx).is_pending());)+ + Poll::Pending + } + } + + Join { + $($idx),+ + }.await + }} +} + +pub async fn run(ctx: impl Context) { + run_procedures! { + a { authentication::initiate(&ctx) } + b { authentication::respond(&ctx) } + c { encryption::initiate(&ctx) } + d { encryption::respond(&ctx) } + e { features::respond(&ctx) } + } +} diff --git a/tools/rootcanal/lmp/src/procedure/secure_simple_pairing.rs b/tools/rootcanal/lmp/src/procedure/secure_simple_pairing.rs new file mode 100644 index 00000000000..acb65296910 --- /dev/null +++ b/tools/rootcanal/lmp/src/procedure/secure_simple_pairing.rs @@ -0,0 +1,620 @@ +// Bluetooth Core, Vol 2, Part C, 4.2.7 + +use std::convert::TryInto; + +use num_traits::{FromPrimitive, ToPrimitive}; + +use crate::either::Either; +use crate::packets::{hci, lmp}; +use crate::procedure::Context; + +use crate::num_hci_command_packets; + +fn has_mitm(requirements: hci::AuthenticationRequirements) -> bool { + use hci::AuthenticationRequirements::*; + + match requirements { + NoBonding | DedicatedBonding | GeneralBonding => false, + NoBondingMitmProtection | DedicatedBondingMitmProtection | GeneralBondingMitmProtection => { + true + } + } +} + +enum AuthenticationMethod { + OutOfBand, + NumericComparaison, + PasskeyEntry, +} + +#[derive(Clone, Copy)] +struct AuthenticationParams { + io_capability: hci::IoCapability, + oob_data_present: hci::OobDataPresent, + authentication_requirements: hci::AuthenticationRequirements, +} + +// Bluetooth Core, Vol 2, Part C, 4.2.7.3 +fn authentication_method( + initiator: AuthenticationParams, + responder: AuthenticationParams, +) -> AuthenticationMethod { + use hci::IoCapability::*; + use hci::OobDataPresent::*; + + if initiator.oob_data_present != NotPresent || responder.oob_data_present != NotPresent { + AuthenticationMethod::OutOfBand + } else if !has_mitm(initiator.authentication_requirements) + && !has_mitm(responder.authentication_requirements) + { + AuthenticationMethod::NumericComparaison + } else if (initiator.io_capability == KeyboardOnly + && responder.io_capability != NoInputNoOutput) + || (responder.io_capability == KeyboardOnly && initiator.io_capability != NoInputNoOutput) + { + AuthenticationMethod::PasskeyEntry + } else { + AuthenticationMethod::NumericComparaison + } +} + +const P192_PUBLIC_KEY_SIZE: usize = 48; + +async fn send_public_key(ctx: &impl Context, transaction_id: u8, key: &[u8; P192_PUBLIC_KEY_SIZE]) { + // TODO: handle error + let _ = ctx + .send_accepted_lmp_packet( + lmp::EncapsulatedHeaderBuilder { + transaction_id, + major_type: 1, + minor_type: 1, + payload_length: P192_PUBLIC_KEY_SIZE as u8, + } + .build(), + ) + .await; + + for chunk in key.chunks(16) { + // TODO: handle error + let _ = ctx + .send_accepted_lmp_packet( + lmp::EncapsulatedPayloadBuilder { transaction_id, data: chunk.try_into().unwrap() } + .build(), + ) + .await; + } +} + +async fn receive_public_key(ctx: &impl Context, transaction_id: u8) -> [u8; P192_PUBLIC_KEY_SIZE] { + let _ = ctx.receive_lmp_packet::().await; + ctx.send_lmp_packet( + lmp::AcceptedBuilder { transaction_id, accepted_opcode: lmp::Opcode::EncapsulatedHeader } + .build(), + ); + + let mut key = [0; P192_PUBLIC_KEY_SIZE]; + + for chunk in key.chunks_mut(16) { + let payload = ctx.receive_lmp_packet::().await; + chunk.copy_from_slice(payload.get_data().as_slice()); + ctx.send_lmp_packet( + lmp::AcceptedBuilder { + transaction_id, + accepted_opcode: lmp::Opcode::EncapsulatedPayload, + } + .build(), + ); + } + + key +} + +const COMMITMENT_VALUE_SIZE: usize = 16; +const NONCE_SIZE: usize = 16; + +async fn receive_commitment(ctx: &impl Context, skip_first: bool) { + let commitment_value = [0; COMMITMENT_VALUE_SIZE]; + + if !skip_first { + let confirm = ctx.receive_lmp_packet::().await; + if confirm.get_commitment_value() != &commitment_value { + todo!(); + } + } + + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { transaction_id: 0, commitment_value }.build(), + ); + + let _pairing_number = ctx.receive_lmp_packet::().await; + // TODO: check pairing number + ctx.send_lmp_packet( + lmp::AcceptedBuilder { + transaction_id: 0, + accepted_opcode: lmp::Opcode::SimplePairingNumber, + } + .build(), + ); + + let nonce = [0; NONCE_SIZE]; + + // TODO: handle error + let _ = ctx + .send_accepted_lmp_packet( + lmp::SimplePairingNumberBuilder { transaction_id: 0, nonce }.build(), + ) + .await; +} + +async fn send_commitment(ctx: &impl Context, skip_first: bool) { + let commitment_value = [0; COMMITMENT_VALUE_SIZE]; + + if !skip_first { + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { transaction_id: 0, commitment_value }.build(), + ); + } + + let confirm = ctx.receive_lmp_packet::().await; + + if confirm.get_commitment_value() != &commitment_value { + todo!(); + } + let nonce = [0; NONCE_SIZE]; + + // TODO: handle error + let _ = ctx + .send_accepted_lmp_packet( + lmp::SimplePairingNumberBuilder { transaction_id: 0, nonce }.build(), + ) + .await; + + let _pairing_number = ctx.receive_lmp_packet::().await; + // TODO: check pairing number + ctx.send_lmp_packet( + lmp::AcceptedBuilder { + transaction_id: 0, + accepted_opcode: lmp::Opcode::SimplePairingNumber, + } + .build(), + ); +} + +async fn user_confirmation_request(ctx: &impl Context) -> Result<(), ()> { + ctx.send_hci_event( + hci::UserConfirmationRequestBuilder { bd_addr: ctx.peer_address(), numeric_value: 0 } + .build(), + ); + + match ctx + .receive_hci_command::>() + .await + { + Either::Left(_) => { + ctx.send_hci_event( + hci::UserConfirmationRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + Ok(()) + } + Either::Right(_) => { + ctx.send_hci_event( + hci::UserConfirmationRequestNegativeReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + Err(()) + } + } +} + +async fn user_passkey_request(ctx: &impl Context) -> Result<(), ()> { + ctx.send_hci_event(hci::UserPasskeyRequestBuilder { bd_addr: ctx.peer_address() }.build()); + + loop { + match ctx + .receive_hci_command::, + hci::SendKeypressNotificationPacket, + >>() + .await + { + Either::Left(Either::Left(_)) => { + ctx.send_hci_event( + hci::UserPasskeyRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Ok(()); + } + Either::Left(Either::Right(_)) => { + ctx.send_hci_event( + hci::UserPasskeyRequestNegativeReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); + } + Either::Right(_) => { + ctx.send_hci_event( + hci::SendKeypressNotificationCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + // TODO: send LmpKeypressNotification + } + } + } +} + +async fn remote_oob_data_request(ctx: &impl Context) -> Result<(), ()> { + ctx.send_hci_event(hci::RemoteOobDataRequestBuilder { bd_addr: ctx.peer_address() }.build()); + + match ctx + .receive_hci_command::>() + .await + { + Either::Left(_) => { + ctx.send_hci_event( + hci::RemoteOobDataRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + Ok(()) + } + Either::Right(_) => { + ctx.send_hci_event( + hci::RemoteOobDataRequestNegativeReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + Err(()) + } + } +} + +const CONFIRMATION_VALUE_SIZE: usize = 16; +const PASSKEY_ENTRY_REPEAT_NUMBER: usize = 20; + +pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { + let initiator = { + ctx.send_hci_event(hci::IoCapabilityRequestBuilder { bd_addr: ctx.peer_address() }.build()); + let reply = ctx.receive_hci_command::().await; + ctx.send_hci_event( + hci::IoCapabilityRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + ctx.send_lmp_packet( + lmp::IoCapabilityReqBuilder { + transaction_id: 0, + io_capabilities: reply.get_io_capability().to_u8().unwrap(), + oob_authentication_data: reply.get_oob_present().to_u8().unwrap(), + authentication_requirement: reply + .get_authentication_requirements() + .to_u8() + .unwrap(), + } + .build(), + ); + + AuthenticationParams { + io_capability: reply.get_io_capability(), + oob_data_present: reply.get_oob_present(), + authentication_requirements: reply.get_authentication_requirements(), + } + }; + let responder = { + let response = ctx.receive_lmp_packet::().await; + + let io_capability = hci::IoCapability::from_u8(response.get_io_capabilities()).unwrap(); + let oob_data_present = + hci::OobDataPresent::from_u8(response.get_oob_authentication_data()).unwrap(); + let authentication_requirements = + hci::AuthenticationRequirements::from_u8(response.get_authentication_requirement()) + .unwrap(); + + ctx.send_hci_event( + hci::IoCapabilityResponseBuilder { + bd_addr: ctx.peer_address(), + io_capability, + oob_data_present, + authentication_requirements, + } + .build(), + ); + + AuthenticationParams { io_capability, oob_data_present, authentication_requirements } + }; + + // Public Key Exchange + { + let public_key = [0; P192_PUBLIC_KEY_SIZE]; + send_public_key(ctx, 0, &public_key).await; + let _key = receive_public_key(ctx, 0).await; + } + + // Authentication Stage 1 + let result: Result<(), ()> = async { + match authentication_method(initiator, responder) { + AuthenticationMethod::NumericComparaison => { + send_commitment(ctx, true).await; + + let _user_confirmation = user_confirmation_request(ctx).await?; + Ok(()) + } + AuthenticationMethod::PasskeyEntry => { + if initiator.io_capability == hci::IoCapability::KeyboardOnly { + let _user_passkey = user_passkey_request(ctx).await?; + } else { + ctx.send_hci_event( + hci::UserPasskeyNotificationBuilder { + bd_addr: ctx.peer_address(), + passkey: 0, + } + .build(), + ); + } + for _ in 0..PASSKEY_ENTRY_REPEAT_NUMBER { + send_commitment(ctx, false).await; + } + Ok(()) + } + AuthenticationMethod::OutOfBand => { + if initiator.oob_data_present != hci::OobDataPresent::NotPresent { + let _remote_oob_data = remote_oob_data_request(ctx).await?; + } + + send_commitment(ctx, false).await; + Ok(()) + } + } + } + .await; + + if result.is_err() { + ctx.send_lmp_packet(lmp::NumericComparaisonFailedBuilder { transaction_id: 0 }.build()); + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); + } + + // Authentication Stage 2 + { + let confirmation_value = [0; CONFIRMATION_VALUE_SIZE]; + + let result = ctx + .send_accepted_lmp_packet( + lmp::DhkeyCheckBuilder { transaction_id: 0, confirmation_value }.build(), + ) + .await; + + if result.is_err() { + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); + } + } + + { + // TODO: check dhkey + let _dhkey = ctx.receive_lmp_packet::().await; + ctx.send_lmp_packet( + lmp::AcceptedBuilder { transaction_id: 0, accepted_opcode: lmp::Opcode::DhkeyCheck } + .build(), + ); + } + + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + Ok(()) +} + +pub async fn respond(ctx: &impl Context, request: lmp::IoCapabilityReqPacket) -> Result<(), ()> { + let initiator = { + let io_capability = hci::IoCapability::from_u8(request.get_io_capabilities()).unwrap(); + let oob_data_present = + hci::OobDataPresent::from_u8(request.get_oob_authentication_data()).unwrap(); + let authentication_requirements = + hci::AuthenticationRequirements::from_u8(request.get_authentication_requirement()) + .unwrap(); + + ctx.send_hci_event( + hci::IoCapabilityResponseBuilder { + bd_addr: ctx.peer_address(), + io_capability, + oob_data_present, + authentication_requirements, + } + .build(), + ); + + AuthenticationParams { io_capability, oob_data_present, authentication_requirements } + }; + + let responder = { + ctx.send_hci_event(hci::IoCapabilityRequestBuilder { bd_addr: ctx.peer_address() }.build()); + let reply = ctx.receive_hci_command::().await; + ctx.send_hci_event( + hci::IoCapabilityRequestReplyCompleteBuilder { + num_hci_command_packets, + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + ctx.send_lmp_packet( + lmp::IoCapabilityResBuilder { + transaction_id: 0, + io_capabilities: reply.get_io_capability().to_u8().unwrap(), + oob_authentication_data: reply.get_oob_present().to_u8().unwrap(), + authentication_requirement: reply + .get_authentication_requirements() + .to_u8() + .unwrap(), + } + .build(), + ); + AuthenticationParams { + io_capability: reply.get_io_capability(), + oob_data_present: reply.get_oob_present(), + authentication_requirements: reply.get_authentication_requirements(), + } + }; + + // Public Key Exchange + { + let public_key = [0; P192_PUBLIC_KEY_SIZE]; + let _key = receive_public_key(ctx, 0).await; + send_public_key(ctx, 0, &public_key).await; + } + + // Authentication Stage 1 + + let negative_user_confirmation = match authentication_method(initiator, responder) { + AuthenticationMethod::NumericComparaison => { + receive_commitment(ctx, true).await; + + let user_confirmation = user_confirmation_request(ctx).await; + user_confirmation.is_err() + } + AuthenticationMethod::PasskeyEntry => { + if responder.io_capability == hci::IoCapability::KeyboardOnly { + // TODO: handle error + let _user_passkey = user_passkey_request(ctx).await; + } else { + ctx.send_hci_event( + hci::UserPasskeyNotificationBuilder { bd_addr: ctx.peer_address(), passkey: 0 } + .build(), + ); + } + for _ in 0..PASSKEY_ENTRY_REPEAT_NUMBER { + receive_commitment(ctx, false).await; + } + false + } + AuthenticationMethod::OutOfBand => { + if responder.oob_data_present != hci::OobDataPresent::NotPresent { + // TODO: handle error + let _remote_oob_data = remote_oob_data_request(ctx).await; + } + + receive_commitment(ctx, false).await; + false + } + }; + + let _dhkey = match ctx + .receive_lmp_packet::>() + .await + { + Either::Left(_) => { + // Numeric comparaison failed + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); + } + Either::Right(dhkey) => dhkey, + }; + + if negative_user_confirmation { + ctx.send_lmp_packet( + lmp::NotAcceptedBuilder { + transaction_id: 0, + not_accepted_opcode: lmp::Opcode::DhkeyCheck, + error_code: hci::ErrorCode::AuthenticationFailure.to_u8().unwrap(), + } + .build(), + ); + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); + } + // Authentication Stage 2 + + let confirmation_value = [0; CONFIRMATION_VALUE_SIZE]; + + ctx.send_lmp_packet( + lmp::AcceptedBuilder { transaction_id: 0, accepted_opcode: lmp::Opcode::DhkeyCheck } + .build(), + ); + + // TODO: handle error + let _ = ctx + .send_accepted_lmp_packet( + lmp::DhkeyCheckBuilder { transaction_id: 0, confirmation_value }.build(), + ) + .await; + + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::Success, + bd_addr: ctx.peer_address(), + } + .build(), + ); + + Ok(()) +} diff --git a/tools/rootcanal/model/controller/dual_mode_controller.cc b/tools/rootcanal/model/controller/dual_mode_controller.cc index b5a2e8fd731..f0df052ce7b 100644 --- a/tools/rootcanal/model/controller/dual_mode_controller.cc +++ b/tools/rootcanal/model/controller/dual_mode_controller.cc @@ -67,9 +67,15 @@ void DualModeController::SendCommandCompleteUnknownOpCodeEvent( std::move(raw_builder_ptr))); } +#ifdef ROOTCANAL_LMP +DualModeController::DualModeController(const std::string& properties_filename, + uint16_t) + : Device(properties_filename) { +#else DualModeController::DualModeController(const std::string& properties_filename, uint16_t num_keys) : Device(properties_filename), security_manager_(num_keys) { +#endif loopback_mode_ = LoopbackMode::NO_LOOPBACK; Address public_address{}; @@ -715,6 +721,9 @@ void DualModeController::RejectSynchronousConnection(CommandView command) { } void DualModeController::IoCapabilityRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::IoCapabilityRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -730,9 +739,13 @@ void DualModeController::IoCapabilityRequestReply(CommandView command) { peer, io_capability, oob_data_present_flag, authentication_requirements); send_event_(bluetooth::hci::IoCapabilityRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::UserConfirmationRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::UserConfirmationRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -743,10 +756,14 @@ void DualModeController::UserConfirmationRequestReply(CommandView command) { send_event_( bluetooth::hci::UserConfirmationRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::UserConfirmationRequestNegativeReply( CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::UserConfirmationRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -758,9 +775,13 @@ void DualModeController::UserConfirmationRequestNegativeReply( send_event_( bluetooth::hci::UserConfirmationRequestNegativeReplyCompleteBuilder:: Create(kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::PinCodeRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::PinCodeRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -777,9 +798,13 @@ void DualModeController::PinCodeRequestReply(CommandView command) { send_event_(bluetooth::hci::PinCodeRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::PinCodeRequestNegativeReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::PinCodeRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -791,9 +816,13 @@ void DualModeController::PinCodeRequestNegativeReply(CommandView command) { send_event_( bluetooth::hci::PinCodeRequestNegativeReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::UserPasskeyRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::UserPasskeyRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -805,9 +834,13 @@ void DualModeController::UserPasskeyRequestReply(CommandView command) { link_layer_controller_.UserPasskeyRequestReply(peer, numeric_value); send_event_(bluetooth::hci::UserPasskeyRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::UserPasskeyRequestNegativeReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::UserPasskeyRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -818,9 +851,13 @@ void DualModeController::UserPasskeyRequestNegativeReply(CommandView command) { send_event_( bluetooth::hci::UserPasskeyRequestNegativeReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::RemoteOobDataRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::RemoteOobDataRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -832,10 +869,14 @@ void DualModeController::RemoteOobDataRequestReply(CommandView command) { send_event_(bluetooth::hci::RemoteOobDataRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::RemoteOobDataRequestNegativeReply( CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::RemoteOobDataRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -846,9 +887,13 @@ void DualModeController::RemoteOobDataRequestNegativeReply( send_event_( bluetooth::hci::RemoteOobDataRequestNegativeReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::IoCapabilityRequestNegativeReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::IoCapabilityRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -861,10 +906,14 @@ void DualModeController::IoCapabilityRequestNegativeReply(CommandView command) { send_event_( bluetooth::hci::IoCapabilityRequestNegativeReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::RemoteOobExtendedDataRequestReply( CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::RemoteOobExtendedDataRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -878,6 +927,7 @@ void DualModeController::RemoteOobExtendedDataRequestReply( send_event_( bluetooth::hci::RemoteOobExtendedDataRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::ReadInquiryResponseTransmitPowerLevel( @@ -893,6 +943,9 @@ void DualModeController::ReadInquiryResponseTransmitPowerLevel( } void DualModeController::SendKeypressNotification(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::SendKeypressNotificationView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -903,6 +956,7 @@ void DualModeController::SendKeypressNotification(CommandView command) { peer, command_view.GetNotificationType()); send_event_(bluetooth::hci::SendKeypressNotificationCompleteBuilder::Create( kNumCommandPackets, status, peer)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::SetEventMaskPage2(CommandView command) { @@ -1037,6 +1091,9 @@ void DualModeController::WriteInquiryScanType(CommandView command) { } void DualModeController::AuthenticationRequested(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::AuthenticationRequestedView::Create( gd_hci::ConnectionManagementCommandView::Create( gd_hci::AclCommandView::Create(command))); @@ -1046,9 +1103,13 @@ void DualModeController::AuthenticationRequested(CommandView command) { send_event_(bluetooth::hci::AuthenticationRequestedStatusBuilder::Create( status, kNumCommandPackets)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::SetConnectionEncryption(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::SetConnectionEncryptionView::Create( gd_hci::ConnectionManagementCommandView::Create( gd_hci::AclCommandView::Create(command))); @@ -1061,6 +1122,7 @@ void DualModeController::SetConnectionEncryption(CommandView command) { send_event_(bluetooth::hci::SetConnectionEncryptionStatusBuilder::Create( status, kNumCommandPackets)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::ChangeConnectionLinkKey(CommandView command) { @@ -1535,6 +1597,9 @@ void DualModeController::RejectConnectionRequest(CommandView command) { } void DualModeController::LinkKeyRequestReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::LinkKeyRequestReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -1543,9 +1608,13 @@ void DualModeController::LinkKeyRequestReply(CommandView command) { auto status = link_layer_controller_.LinkKeyRequestReply(addr, key); send_event_(bluetooth::hci::LinkKeyRequestReplyCompleteBuilder::Create( kNumCommandPackets, status, addr)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::LinkKeyRequestNegativeReply(CommandView command) { +#ifdef ROOTCANAL_LMP + link_layer_controller_.ForwardToLm(command); +#else auto command_view = gd_hci::LinkKeyRequestNegativeReplyView::Create( gd_hci::SecurityCommandView::Create(command)); ASSERT(command_view.IsValid()); @@ -1554,6 +1623,7 @@ void DualModeController::LinkKeyRequestNegativeReply(CommandView command) { send_event_( bluetooth::hci::LinkKeyRequestNegativeReplyCompleteBuilder::Create( kNumCommandPackets, status, addr)); +#endif /* ROOTCANAL_LMP */ } void DualModeController::DeleteStoredLinkKey(CommandView command) { @@ -1566,11 +1636,15 @@ void DualModeController::DeleteStoredLinkKey(CommandView command) { auto flag = command_view.GetDeleteAllFlag(); if (flag == gd_hci::DeleteStoredLinkKeyDeleteAllFlag::SPECIFIED_BD_ADDR) { Address addr = command_view.GetBdAddr(); +#ifndef ROOTCANAL_LMP deleted_keys = security_manager_.DeleteKey(addr); +#endif /* !ROOTCANAL_LMP */ } if (flag == gd_hci::DeleteStoredLinkKeyDeleteAllFlag::ALL) { +#ifndef ROOTCANAL_LMP security_manager_.DeleteAllKeys(); +#endif /* !ROOTCANAL_LMP */ } send_event_(bluetooth::hci::DeleteStoredLinkKeyCompleteBuilder::Create( diff --git a/tools/rootcanal/model/controller/dual_mode_controller.h b/tools/rootcanal/model/controller/dual_mode_controller.h index 60eb1a9edba..8b9dd6d6cc5 100644 --- a/tools/rootcanal/model/controller/dual_mode_controller.h +++ b/tools/rootcanal/model/controller/dual_mode_controller.h @@ -29,7 +29,9 @@ #include "link_layer_controller.h" #include "model/devices/device.h" #include "model/setup/async_manager.h" +#ifndef ROOTCANAL_LMP #include "security_manager.h" +#endif /* !ROOTCANAL_LMP */ namespace rootcanal { @@ -645,7 +647,9 @@ class DualModeController : public Device { bluetooth::hci::LoopbackMode loopback_mode_; +#ifndef ROOTCANAL_LMP SecurityManager security_manager_; +#endif /* ROOTCANAL_LMP */ DualModeController(const DualModeController& cmdPckt) = delete; DualModeController& operator=(const DualModeController& cmdPckt) = delete; diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc index 3b660d6d610..ebdf92102fa 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.cc +++ b/tools/rootcanal/model/controller/link_layer_controller.cc @@ -17,6 +17,9 @@ #include "link_layer_controller.h" #include +#ifdef ROOTCANAL_LMP +#include +#endif /* ROOTCANAL_LMP */ #include "crypto_toolbox/crypto_toolbox.h" #include "os/log.h" @@ -61,6 +64,70 @@ void LinkLayerController::SendLeLinkLayerPacketWithRssi( }); } +#ifdef ROOTCANAL_LMP +LinkLayerController::LinkLayerController(const DeviceProperties& properties) + : properties_(properties), lm_(nullptr, link_manager_destroy) { + auto ops = (struct LinkManagerOps){ + .user_pointer = this, + .get_handle = + [](void* user, const uint8_t(*address)[6]) { + auto controller = static_cast(user); + + return controller->connections_.GetHandleOnlyAddress( + Address(*address)); + }, + + .get_address = + [](void* user, uint16_t handle, uint8_t(*result)[6]) { + auto controller = static_cast(user); + + auto address = + controller->connections_.GetAddress(handle).GetAddress(); + std::copy(address.data(), address.data() + 6, + reinterpret_cast(result)); + }, + + .extended_features = + [](void* user, uint8_t features_page) { + auto controller = static_cast(user); + + return controller->properties_.GetExtendedFeatures(features_page); + }, + + .send_hci_event = + [](void* user, const uint8_t* data, uintptr_t len) { + auto controller = static_cast(user); + + auto event_code = static_cast(data[0]); + auto payload = std::make_unique( + std::vector(data + 2, data + len)); + + controller->send_event_(bluetooth::hci::EventBuilder::Create( + event_code, std::move(payload))); + }, + + .send_lmp_packet = + [](void* user, const uint8_t(*to)[6], const uint8_t* data, + uintptr_t len) { + auto controller = static_cast(user); + + auto payload = std::make_unique( + std::vector(data, data + len)); + + Address source = controller->properties_.GetAddress(); + Address dest(*to); + + controller->SendLinkLayerPacket(model::packets::LmpBuilder::Create( + source, dest, std::move(payload))); + }}; + + lm_.reset(link_manager_create(ops)); +} +#else +LinkLayerController::LinkLayerController(const DeviceProperties& properties) + : properties_(properties) {} +#endif + void LinkLayerController::SendLeLinkLayerPacket( std::unique_ptr packet) { std::shared_ptr shared_packet = @@ -294,20 +361,17 @@ void LinkLayerController::IncomingPacketWithRssi( case model::packets::PacketType::DISCONNECT: IncomingDisconnectPacket(incoming); break; +#ifdef ROOTCANAL_LMP + case model::packets::PacketType::LMP: + IncomingLmpPacket(incoming); + break; +#else case model::packets::PacketType::ENCRYPT_CONNECTION: IncomingEncryptConnection(incoming); break; case model::packets::PacketType::ENCRYPT_CONNECTION_RESPONSE: IncomingEncryptConnectionResponse(incoming); break; - case model::packets::PacketType::INQUIRY: - if (inquiry_scans_enabled_) { - IncomingInquiryPacket(incoming, rssi); - } - break; - case model::packets::PacketType::INQUIRY_RESPONSE: - IncomingInquiryResponsePacket(incoming); - break; case model::packets::PacketType::IO_CAPABILITY_REQUEST: IncomingIoCapabilityRequestPacket(incoming); break; @@ -317,6 +381,30 @@ void LinkLayerController::IncomingPacketWithRssi( case model::packets::PacketType::IO_CAPABILITY_NEGATIVE_RESPONSE: IncomingIoCapabilityNegativeResponsePacket(incoming); break; + case PacketType::KEYPRESS_NOTIFICATION: + IncomingKeypressNotificationPacket(incoming); + break; + case (model::packets::PacketType::PASSKEY): + IncomingPasskeyPacket(incoming); + break; + case (model::packets::PacketType::PASSKEY_FAILED): + IncomingPasskeyFailedPacket(incoming); + break; + case (model::packets::PacketType::PIN_REQUEST): + IncomingPinRequestPacket(incoming); + break; + case (model::packets::PacketType::PIN_RESPONSE): + IncomingPinResponsePacket(incoming); + break; +#endif /* ROOTCANAL_LMP */ + case model::packets::PacketType::INQUIRY: + if (inquiry_scans_enabled_) { + IncomingInquiryPacket(incoming, rssi); + } + break; + case model::packets::PacketType::INQUIRY_RESPONSE: + IncomingInquiryResponsePacket(incoming); + break; case PacketType::ISO: IncomingIsoPacket(incoming); break; @@ -326,9 +414,6 @@ void LinkLayerController::IncomingPacketWithRssi( case PacketType::ISO_CONNECTION_RESPONSE: IncomingIsoConnectionResponsePacket(incoming); break; - case PacketType::KEYPRESS_NOTIFICATION: - IncomingKeypressNotificationPacket(incoming); - break; case model::packets::PacketType::LE_ADVERTISEMENT: if (le_scan_enable_ != bluetooth::hci::OpCode::NONE || le_connect_) { IncomingLeAdvertisementPacket(incoming, rssi); @@ -379,18 +464,6 @@ void LinkLayerController::IncomingPacketWithRssi( case model::packets::PacketType::PAGE_REJECT: IncomingPageRejectPacket(incoming); break; - case (model::packets::PacketType::PASSKEY): - IncomingPasskeyPacket(incoming); - break; - case (model::packets::PacketType::PASSKEY_FAILED): - IncomingPasskeyFailedPacket(incoming); - break; - case (model::packets::PacketType::PIN_REQUEST): - IncomingPinRequestPacket(incoming); - break; - case (model::packets::PacketType::PIN_RESPONSE): - IncomingPinResponsePacket(incoming); - break; case (model::packets::PacketType::REMOTE_NAME_REQUEST): IncomingRemoteNameRequest(incoming); break; @@ -439,7 +512,6 @@ void LinkLayerController::IncomingPacketWithRssi( case model::packets::PacketType::SCO_DISCONNECT: IncomingScoDisconnect(incoming); break; - default: LOG_WARN("Dropping unhandled packet of type %s", model::packets::PacketTypeText(incoming.GetType()).c_str()); @@ -697,8 +769,13 @@ void LinkLayerController::IncomingDisconnectPacket( uint8_t reason = disconnect.GetReason(); SendDisconnectionCompleteEvent(handle, reason); +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_remove_link( + lm_.get(), reinterpret_cast(peer.data()))); +#endif } +#ifndef ROOTCANAL_LMP void LinkLayerController::IncomingEncryptConnection( model::packets::LinkLayerPacketView incoming) { LOG_INFO("IncomingEncryptConnection"); @@ -742,6 +819,7 @@ void LinkLayerController::IncomingEncryptConnectionResponse( ErrorCode::SUCCESS, handle, bluetooth::hci::EncryptionEnabled::ON)); } } +#endif /* !ROOTCANAL_LMP */ void LinkLayerController::IncomingInquiryPacket( model::packets::LinkLayerPacketView incoming, uint8_t rssi) { @@ -866,6 +944,7 @@ void LinkLayerController::IncomingInquiryResponsePacket( } } +#ifndef ROOTCANAL_LMP void LinkLayerController::IncomingIoCapabilityRequestPacket( model::packets::LinkLayerPacketView incoming) { Address peer = incoming.GetSourceAddress(); @@ -987,6 +1066,7 @@ void LinkLayerController::IncomingIoCapabilityNegativeResponsePacket( incoming.GetSourceAddress())); } } +#endif /* !ROOTCANAL_LMP */ void LinkLayerController::IncomingIsoPacket(LinkLayerPacketView incoming) { auto iso = IsoDataPacketView::Create(incoming); @@ -1206,6 +1286,7 @@ void LinkLayerController::IncomingIsoConnectionResponsePacket( } } +#ifndef ROOTCANAL_LMP void LinkLayerController::IncomingKeypressNotificationPacket( model::packets::LinkLayerPacketView incoming) { auto keypress = model::packets::KeypressNotificationView::Create(incoming); @@ -1224,6 +1305,7 @@ void LinkLayerController::IncomingKeypressNotificationPacket( notification_type))); } } +#endif /* !ROOTCANAL_LMP */ static bool rpa_matches_irk( Address rpa, std::array irk) { @@ -1557,6 +1639,21 @@ void LinkLayerController::IncomingScoDisconnect( } } +#ifdef ROOTCANAL_LMP +void LinkLayerController::IncomingLmpPacket( + model::packets::LinkLayerPacketView incoming) { + Address address = incoming.GetSourceAddress(); + auto request = model::packets::LmpView::Create(incoming); + ASSERT(request.IsValid()); + auto payload = request.GetPayload(); + auto packet = std::vector(payload.begin(), payload.end()); + + ASSERT(link_manager_ingest_lmp( + lm_.get(), reinterpret_cast(address.data()), packet.data(), + packet.size())); +} +#endif /* ROOTCANAL_LMP */ + uint16_t LinkLayerController::HandleLeConnection( AddressWithType address, AddressWithType own_address, uint8_t role, uint16_t connection_interval, uint16_t connection_latency, @@ -1931,6 +2028,7 @@ void LinkLayerController::IncomingLeScanResponsePacket( } } +#ifndef ROOTCANAL_LMP void LinkLayerController::IncomingPasskeyPacket( model::packets::LinkLayerPacketView incoming) { auto passkey = model::packets::PasskeyView::Create(incoming); @@ -2064,6 +2162,7 @@ void LinkLayerController::IncomingPinResponsePacket( }); } } +#endif /* !ROOTCANAL_LMP */ void LinkLayerController::IncomingPagePacket( model::packets::LinkLayerPacketView incoming) { @@ -2078,6 +2177,12 @@ void LinkLayerController::IncomingPagePacket( incoming.GetSourceAddress().ToString().c_str()); } +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_add_link(lm_.get(), + reinterpret_cast( + incoming.GetSourceAddress().data()))); +#endif + bluetooth::hci::Address source_address{}; bluetooth::hci::Address::FromString(page.GetSourceAddress().ToString(), source_address); @@ -2107,7 +2212,9 @@ void LinkLayerController::IncomingPageResponsePacket( model::packets::LinkLayerPacketView incoming) { Address peer = incoming.GetSourceAddress(); LOG_INFO("%s", peer.ToString().c_str()); +#ifndef ROOTCANAL_LMP bool awaiting_authentication = connections_.AuthenticatePendingConnection(); +#endif /* !ROOTCANAL_LMP */ uint16_t handle = connections_.CreateConnection(peer, incoming.GetDestinationAddress()); if (handle == kReservedHandle) { @@ -2120,16 +2227,21 @@ void LinkLayerController::IncomingPageResponsePacket( bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED)); } +#ifndef ROOTCANAL_LMP if (awaiting_authentication) { ScheduleTask(kNoDelayMs, [this, peer, handle]() { HandleAuthenticationRequest(peer, handle); }); } +#endif /* !ROOTCANAL_LMP */ } void LinkLayerController::TimerTick() { if (inquiry_timer_task_id_ != kInvalidTaskId) Inquiry(); LeAdvertising(); +#ifdef ROOTCANAL_LMP + link_manager_tick(lm_.get()); +#endif /* ROOTCANAL_LMP */ } void LinkLayerController::Close() { @@ -2217,6 +2329,12 @@ void LinkLayerController::RegisterTaskCancel( cancel_task_ = task_cancel; } +#ifdef ROOTCANAL_LMP +void LinkLayerController::ForwardToLm(bluetooth::hci::CommandView command) { + auto packet = std::vector(command.begin(), command.end()); + ASSERT(link_manager_ingest_hci(lm_.get(), packet.data(), packet.size())); +} +#else void LinkLayerController::StartSimplePairing(const Address& address) { // IO Capability Exchange (See the Diagram in the Spec) if (properties_.IsUnmasked(EventCode::IO_CAPABILITY_REQUEST)) { @@ -2661,6 +2779,7 @@ ErrorCode LinkLayerController::SetConnectionEncryption( }); return ErrorCode::SUCCESS; } +#endif /* ROOTCANAL_LMP */ ErrorCode LinkLayerController::AcceptConnectionRequest(const Address& bd_addr, bool try_role_switch) { @@ -2770,6 +2889,11 @@ ErrorCode LinkLayerController::CreateConnection(const Address& addr, uint16_t, addr, properties_.GetAuthenticationEnable() == 1)) { return ErrorCode::CONTROLLER_BUSY; } +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_add_link( + lm_.get(), reinterpret_cast(addr.data()))); +#endif + SendLinkLayerPacket(model::packets::PageBuilder::Create( properties_.GetAddress(), addr, properties_.GetClassOfDevice(), allow_role_switch)); @@ -2839,6 +2963,10 @@ ErrorCode LinkLayerController::Disconnect(uint16_t handle, uint8_t reason) { connections_.Disconnect(handle); SendDisconnectionCompleteEvent(handle, reason); +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_remove_link( + lm_.get(), reinterpret_cast(remote.GetAddress().data()))); +#endif return ErrorCode::SUCCESS; } diff --git a/tools/rootcanal/model/controller/link_layer_controller.h b/tools/rootcanal/model/controller/link_layer_controller.h index c5ab9ff7547..1048fa37ae8 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.h +++ b/tools/rootcanal/model/controller/link_layer_controller.h @@ -24,7 +24,14 @@ #include "model/devices/device_properties.h" #include "model/setup/async_manager.h" #include "packets/link_layer_packets.h" + +#ifdef ROOTCANAL_LMP +extern "C" { +struct LinkManager; +} +#else #include "security_manager.h" +#endif /* ROOTCANAL_LMP */ namespace rootcanal { @@ -37,8 +44,7 @@ class LinkLayerController { public: static constexpr size_t kIrkSize = 16; - LinkLayerController(const DeviceProperties& properties) - : properties_(properties) {} + LinkLayerController(const DeviceProperties& properties); ErrorCode SendCommandToRemoteByAddress( OpCode opcode, bluetooth::packet::PacketView args, const Address& remote); @@ -49,6 +55,9 @@ class LinkLayerController { ErrorCode SendScoToRemote(bluetooth::hci::ScoView sco_packet); ErrorCode SendAclToRemote(bluetooth::hci::AclView acl_packet); +#ifdef ROOTCANAL_LMP + void ForwardToLm(bluetooth::hci::CommandView command); +#else void StartSimplePairing(const Address& address); void AuthenticateRemoteStage1(const Address& address, PairingType pairing_type); @@ -86,6 +95,7 @@ class LinkLayerController { ErrorCode SetConnectionEncryption(uint16_t handle, uint8_t encryption_enable); void HandleAuthenticationRequest(const Address& address, uint16_t handle); ErrorCode AuthenticationRequested(uint16_t handle); +#endif /* ROOTCANAL_LMP */ ErrorCode AcceptConnectionRequest(const Address& addr, bool try_role_switch); void MakePeripheralConnection(const Address& addr, bool try_role_switch); @@ -393,19 +403,27 @@ class LinkLayerController { uint8_t rssi); void IncomingInquiryResponsePacket( model::packets::LinkLayerPacketView packet); +#ifdef ROOTCANAL_LMP + void IncomingLmpPacket(model::packets::LinkLayerPacketView packet); +#else void IncomingIoCapabilityRequestPacket( model::packets::LinkLayerPacketView packet); void IncomingIoCapabilityResponsePacket( model::packets::LinkLayerPacketView packet); void IncomingIoCapabilityNegativeResponsePacket( model::packets::LinkLayerPacketView packet); + void IncomingKeypressNotificationPacket( + model::packets::LinkLayerPacketView packet); + void IncomingPasskeyPacket(model::packets::LinkLayerPacketView packet); + void IncomingPasskeyFailedPacket(model::packets::LinkLayerPacketView packet); + void IncomingPinRequestPacket(model::packets::LinkLayerPacketView packet); + void IncomingPinResponsePacket(model::packets::LinkLayerPacketView packet); +#endif /* ROOTCANAL_LMP */ void IncomingIsoPacket(model::packets::LinkLayerPacketView packet); void IncomingIsoConnectionRequestPacket( model::packets::LinkLayerPacketView packet); void IncomingIsoConnectionResponsePacket( model::packets::LinkLayerPacketView packet); - void IncomingKeypressNotificationPacket( - model::packets::LinkLayerPacketView packet); void IncomingLeAdvertisementPacket(model::packets::LinkLayerPacketView packet, uint8_t rssi); void IncomingLeConnectPacket(model::packets::LinkLayerPacketView packet); @@ -427,10 +445,6 @@ class LinkLayerController { void IncomingPagePacket(model::packets::LinkLayerPacketView packet); void IncomingPageRejectPacket(model::packets::LinkLayerPacketView packet); void IncomingPageResponsePacket(model::packets::LinkLayerPacketView packet); - void IncomingPasskeyPacket(model::packets::LinkLayerPacketView packet); - void IncomingPasskeyFailedPacket(model::packets::LinkLayerPacketView packet); - void IncomingPinRequestPacket(model::packets::LinkLayerPacketView packet); - void IncomingPinResponsePacket(model::packets::LinkLayerPacketView packet); void IncomingReadRemoteLmpFeatures( model::packets::LinkLayerPacketView packet); void IncomingReadRemoteLmpFeaturesResponse( @@ -527,8 +541,11 @@ class LinkLayerController { uint8_t le_peer_address_type_{}; // Classic state - +#ifdef ROOTCANAL_LMP + std::unique_ptr lm_; +#else SecurityManager security_manager_{10}; +#endif /* ROOTCANAL_LMP */ std::chrono::steady_clock::time_point last_inquiry_; model::packets::InquiryType inquiry_mode_{ model::packets::InquiryType::STANDARD}; diff --git a/tools/rootcanal/packets/link_layer_packets.pdl b/tools/rootcanal/packets/link_layer_packets.pdl index c1336e7a2d0..45cdb5114f9 100644 --- a/tools/rootcanal/packets/link_layer_packets.pdl +++ b/tools/rootcanal/packets/link_layer_packets.pdl @@ -57,6 +57,8 @@ enum PacketType : 8 { SCO_CONNECTION_RESPONSE = 0x31, SCO_DISCONNECT = 0x32, RSSI_WRAPPER = 0x33, + + LMP = 0x34, } packet LinkLayerPacket { @@ -412,3 +414,7 @@ packet RssiWrapper : LinkLayerPacket (type = RSSI_WRAPPER) { rssi : 8, _payload_, } + +packet Lmp : LinkLayerPacket (type = LMP) { + _payload_, +} -- GitLab From e6fb0494785f8758c9f1a4d3875e11e2fcf0e6e5 Mon Sep 17 00:00:00 2001 From: Duy Truong Date: Thu, 5 May 2022 14:08:50 -0700 Subject: [PATCH 057/998] rootcanal: correctly handle error conditions in test command add_phy. Make it so that AddPhy does not segfaults when called with no argument and send a proper response when called with correct arguments. Test: manually send add_phy commands with correct/incorrect arguments. Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Idfd84ed5e994469ac69ec30e408abe3662ec7290 Change-Id: Idfd84ed5e994469ac69ec30e408abe3662ec7290 --- tools/rootcanal/model/setup/test_command_handler.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/rootcanal/model/setup/test_command_handler.cc b/tools/rootcanal/model/setup/test_command_handler.cc index 5c959318220..b680558624b 100644 --- a/tools/rootcanal/model/setup/test_command_handler.cc +++ b/tools/rootcanal/model/setup/test_command_handler.cc @@ -188,15 +188,19 @@ void TestCommandHandler::Del(const vector& args) { } void TestCommandHandler::AddPhy(const vector& args) { - if (args[0] == "LOW_ENERGY") { + if (args.size() != 1) { + response_string_ = "TestCommandHandler 'add_phy' takes one argument"; + } else if (args[0] == "LOW_ENERGY") { model_.AddPhy(Phy::Type::LOW_ENERGY); + response_string_ = "TestCommandHandler 'add_phy' called with LOW_ENERGY"; } else if (args[0] == "BR_EDR") { model_.AddPhy(Phy::Type::BR_EDR); + response_string_ = "TestCommandHandler 'add_phy' called with BR_EDR"; } else { response_string_ = "TestCommandHandler 'add_phy' with unrecognized type " + args[0]; - send_response_(response_string_); } + send_response_(response_string_); } void TestCommandHandler::DelPhy(const vector& args) { -- GitLab From 13193bbfb6e7cdceaa6c8978fe9da421ab32b8c5 Mon Sep 17 00:00:00 2001 From: David Duarte Date: Mon, 9 May 2022 09:40:48 +0000 Subject: [PATCH 058/998] RootCanal: Only remove BR_EDR links from LMP Test: gd/cert/run LeAclManagerTest Test: gd/cert/run SecurityTest Fix: 231745328 Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Iefb3592b5869459adfd0196f9b3c20a2c4cf3745 Change-Id: Iefb3592b5869459adfd0196f9b3c20a2c4cf3745 --- .../model/controller/link_layer_controller.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc index ebdf92102fa..127bf74c24b 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.cc +++ b/tools/rootcanal/model/controller/link_layer_controller.cc @@ -764,14 +764,19 @@ void LinkLayerController::IncomingDisconnectPacket( peer.ToString().c_str()); return; } +#ifdef ROOTCANAL_LMP + auto is_br_edr = connections_.GetPhyType(handle) == Phy::Type::BR_EDR; +#endif ASSERT_LOG(connections_.Disconnect(handle), "GetHandle() returned invalid handle %hx", handle); uint8_t reason = disconnect.GetReason(); SendDisconnectionCompleteEvent(handle, reason); #ifdef ROOTCANAL_LMP - ASSERT(link_manager_remove_link( - lm_.get(), reinterpret_cast(peer.data()))); + if (is_br_edr) { + ASSERT(link_manager_remove_link( + lm_.get(), reinterpret_cast(peer.data()))); + } #endif } @@ -2937,8 +2942,9 @@ ErrorCode LinkLayerController::Disconnect(uint16_t handle, uint8_t reason) { } const AddressWithType remote = connections_.GetAddress(handle); + auto is_br_edr = connections_.GetPhyType(handle) == Phy::Type::BR_EDR; - if (connections_.GetPhyType(handle) == Phy::Type::BR_EDR) { + if (is_br_edr) { LOG_INFO("Disconnecting ACL connection with %s", remote.ToString().c_str()); uint16_t sco_handle = connections_.GetScoHandle(remote.GetAddress()); @@ -2964,8 +2970,11 @@ ErrorCode LinkLayerController::Disconnect(uint16_t handle, uint8_t reason) { connections_.Disconnect(handle); SendDisconnectionCompleteEvent(handle, reason); #ifdef ROOTCANAL_LMP - ASSERT(link_manager_remove_link( - lm_.get(), reinterpret_cast(remote.GetAddress().data()))); + if (is_br_edr) { + ASSERT(link_manager_remove_link( + lm_.get(), + reinterpret_cast(remote.GetAddress().data()))); + } #endif return ErrorCode::SUCCESS; } -- GitLab From 604fc4a40a50b37918f668181eb7b3dfe8ae1aaf Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Mon, 9 May 2022 11:43:45 -0700 Subject: [PATCH 059/998] RootCanal: Use weak_ptr for Phy, Device Bug: 227487676 Bug: 241962982 Test: cert/run Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Ifb833c6109fc2dc4b0a1c0a14be185ecb1fd8296 Change-Id: Ifb833c6109fc2dc4b0a1c0a14be185ecb1fd8296 --- tools/rootcanal/model/devices/device.cc | 12 +++++--- tools/rootcanal/model/devices/device.h | 2 +- tools/rootcanal/model/setup/test_model.cc | 37 ++++++++++++++--------- tools/rootcanal/model/setup/test_model.h | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/tools/rootcanal/model/devices/device.cc b/tools/rootcanal/model/devices/device.cc index 3bcf30b94f7..f5fa8bba7ec 100644 --- a/tools/rootcanal/model/devices/device.cc +++ b/tools/rootcanal/model/devices/device.cc @@ -31,7 +31,8 @@ void Device::RegisterPhyLayer(std::shared_ptr phy) { } void Device::UnregisterPhyLayers() { - for (auto phy : phy_layers_) { + for (auto weak_phy : phy_layers_) { + auto phy = weak_phy.lock(); if (phy != nullptr) { phy->Unregister(); } @@ -40,7 +41,8 @@ void Device::UnregisterPhyLayers() { } void Device::UnregisterPhyLayer(Phy::Type phy_type, uint32_t factory_id) { - for (auto& phy : phy_layers_) { + for (auto& weak_phy : phy_layers_) { + auto phy = weak_phy.lock(); if (phy != nullptr && phy->IsFactoryId(factory_id) && phy->GetType() == phy_type) { phy->Unregister(); @@ -59,7 +61,8 @@ bool Device::IsAdvertisementAvailable() const { void Device::SendLinkLayerPacket( std::shared_ptr to_send, Phy::Type phy_type) { - for (auto phy : phy_layers_) { + for (auto weak_phy : phy_layers_) { + auto phy = weak_phy.lock(); if (phy != nullptr && phy->GetType() == phy_type) { phy->Send(to_send); } @@ -68,7 +71,8 @@ void Device::SendLinkLayerPacket( void Device::SendLinkLayerPacket(model::packets::LinkLayerPacketView to_send, Phy::Type phy_type) { - for (auto phy : phy_layers_) { + for (auto weak_phy : phy_layers_) { + auto phy = weak_phy.lock(); if (phy != nullptr && phy->GetType() == phy_type) { phy->Send(to_send); } diff --git a/tools/rootcanal/model/devices/device.h b/tools/rootcanal/model/devices/device.h index 25d10247dde..541db6da9ba 100644 --- a/tools/rootcanal/model/devices/device.h +++ b/tools/rootcanal/model/devices/device.h @@ -85,7 +85,7 @@ class Device { void RegisterCloseCallback(std::function); protected: - std::vector> phy_layers_; + std::vector> phy_layers_; std::chrono::steady_clock::time_point last_advertisement_; diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc index 27461805199..37da7553cb9 100644 --- a/tools/rootcanal/model/setup/test_model.cc +++ b/tools/rootcanal/model/setup/test_model.cc @@ -88,13 +88,13 @@ void TestModel::Del(size_t dev_index) { schedule_task_(model_user_id_, std::chrono::milliseconds(0), [this, dev_index]() { devices_[dev_index]->UnregisterPhyLayers(); - devices_[dev_index] = nullptr; + devices_[dev_index].reset(); }); } size_t TestModel::AddPhy(Phy::Type phy_type) { size_t factory_id = phys_.size(); - phys_.emplace_back(phy_type, factory_id); + phys_.push_back(std::make_shared(phy_type, factory_id)); return factory_id; } @@ -103,9 +103,11 @@ void TestModel::DelPhy(size_t phy_index) { LOG_WARN("Unknown phy at index %zu", phy_index); return; } - schedule_task_( - model_user_id_, std::chrono::milliseconds(0), - [this, phy_index]() { phys_[phy_index].UnregisterAllPhyLayers(); }); + schedule_task_(model_user_id_, std::chrono::milliseconds(0), + [this, phy_index]() { + phys_[phy_index]->UnregisterAllPhyLayers(); + phys_[phy_index].reset(); + }); } void TestModel::AddDeviceToPhy(size_t dev_index, size_t phy_index) { @@ -118,9 +120,13 @@ void TestModel::AddDeviceToPhy(size_t dev_index, size_t phy_index) { return; } auto dev = devices_[dev_index]; - dev->RegisterPhyLayer(phys_[phy_index].GetPhyLayer( - [dev](model::packets::LinkLayerPacketView packet) { - dev->IncomingPacket(std::move(packet)); + std::weak_ptr weak_dev = dev; + dev->RegisterPhyLayer(phys_[phy_index]->GetPhyLayer( + [weak_dev](model::packets::LinkLayerPacketView packet) { + auto device = weak_dev.lock(); + if (device != nullptr) { + device->IncomingPacket(std::move(packet)); + } }, dev_index)); } @@ -137,8 +143,8 @@ void TestModel::DelDeviceFromPhy(size_t dev_index, size_t phy_index) { schedule_task_(model_user_id_, std::chrono::milliseconds(0), [this, dev_index, phy_index]() { devices_[dev_index]->UnregisterPhyLayer( - phys_[phy_index].GetType(), - phys_[phy_index].GetFactoryId()); + phys_[phy_index]->GetType(), + phys_[phy_index]->GetFactoryId()); }); } @@ -150,7 +156,7 @@ void TestModel::AddLinkLayerConnection(std::shared_ptr dev, AsyncUserId user_id = get_user_id_(); for (size_t i = 0; i < phys_.size(); i++) { - if (phy_type == phys_[i].GetType()) { + if (phy_type == phys_[i]->GetType()) { AddDeviceToPhy(index, i); } } @@ -202,8 +208,7 @@ void TestModel::OnConnectionClosed(size_t index, AsyncUserId user_id) { } cancel_tasks_from_user_(user_id); - devices_[index]->UnregisterPhyLayers(); - devices_[index] = nullptr; + Del(index); } void TestModel::SetDeviceAddress(size_t index, Address address) { @@ -228,7 +233,11 @@ const std::string& TestModel::List() { list_string_ += " Phys: \r\n"; for (size_t i = 0; i < phys_.size(); i++) { list_string_ += " " + std::to_string(i) + ":"; - list_string_ += phys_[i].ToString() + " \r\n"; + if (phys_[i] == nullptr) { + list_string_ += " deleted \r\n"; + } else { + list_string_ += phys_[i]->ToString() + " \r\n"; + } } return list_string_; } diff --git a/tools/rootcanal/model/setup/test_model.h b/tools/rootcanal/model/setup/test_model.h index 263346c9f8e..6449b62b14f 100644 --- a/tools/rootcanal/model/setup/test_model.h +++ b/tools/rootcanal/model/setup/test_model.h @@ -100,7 +100,7 @@ class TestModel { void Reset(); private: - std::vector phys_; + std::vector> phys_; std::vector> devices_; std::string list_string_; -- GitLab From e233730a187bc6c57c89e6fd573dc3edaf186676 Mon Sep 17 00:00:00 2001 From: Julien Desprez Date: Mon, 9 May 2022 12:35:15 -0700 Subject: [PATCH 060/998] Mark a few bluetooth unit tests as isolated:false Seems like it fails with isolated:true, might need some fixes in test assumptions. Test: presubmit Bug: 178498003 Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I4e1e526969cb6cb38a6c989ce4f517893e644816 Change-Id: I4e1e526969cb6cb38a6c989ce4f517893e644816 --- system/bta/Android.bp | 6 ++++++ system/gd/Android.bp | 5 ++--- tools/rootcanal/Android.bp | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/system/bta/Android.bp b/system/bta/Android.bp index 8129074ceb4..8141e0bb3e0 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -610,6 +610,8 @@ cc_test { "mts_defaults", ], host_supported: true, + // TODO(b/231993739): Reenable isolated:true by deleting the explicit disable below + isolated: false, include_dirs: [ "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/bta/include", @@ -700,6 +702,8 @@ cc_test { "mts_defaults", ], host_supported: true, + // TODO(b/231993739): Reenable isolated:true by deleting the explicit disable below + isolated: false, include_dirs: [ "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/bta/include", @@ -817,6 +821,8 @@ cc_test { "mts_defaults", ], host_supported: true, + // TODO(b/231993739): Reenable isolated:true by deleting the explicit disable below + isolated: false, include_dirs: [ "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/bta/include", diff --git a/system/gd/Android.bp b/system/gd/Android.bp index df5e67ab506..7cda7a569f2 100644 --- a/system/gd/Android.bp +++ b/system/gd/Android.bp @@ -325,9 +325,8 @@ cc_test { "mts_defaults", ], host_supported: true, - test_options: { - unit_test: true, - }, + // TODO(b/231993739): Reenable isolated:true by deleting the explicit disable below + isolated: false, target: { linux: { srcs: [ diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp index 2e705bf615c..2c62d8dfa45 100644 --- a/tools/rootcanal/Android.bp +++ b/tools/rootcanal/Android.bp @@ -124,6 +124,8 @@ cc_test_host { "clang_file_coverage", "clang_coverage_bin", ], + // TODO(b/231993739): Reenable isolated:true by deleting the explicit disable below + isolated: false, srcs: [ "test/async_manager_unittest.cc", "test/h4_parser_unittest.cc", -- GitLab From 5733845b1f9e48360f6223b0e07624fea37b8acd Mon Sep 17 00:00:00 2001 From: Erwin Jansen Date: Wed, 11 May 2022 13:57:14 -0700 Subject: [PATCH 061/998] Fix crash in emulator unit tests We strengthen the invariants in the async manager to make sure a task is not running when it is being cancelled. Without this it becomes incredibly difficult to guarantee that a task is: - Not actively running - Scheduled to be called This makes sure that we do not delete the TestModel while a task is actively running. Test: Additional unit test to validate expected behavior Bug: 232262266 Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I6decf29193b0ee041b1d64a14de62b63528d223a Change-Id: I6decf29193b0ee041b1d64a14de62b63528d223a --- tools/rootcanal/model/setup/async_manager.cc | 23 ++++++++-- tools/rootcanal/model/setup/async_manager.h | 20 ++++----- tools/rootcanal/model/setup/test_model.cc | 4 ++ tools/rootcanal/model/setup/test_model.h | 2 +- .../rootcanal/test/async_manager_unittest.cc | 44 ++++++++++++++++++- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/tools/rootcanal/model/setup/async_manager.cc b/tools/rootcanal/model/setup/async_manager.cc index 01447ec466e..5ae004a395b 100644 --- a/tools/rootcanal/model/setup/async_manager.cc +++ b/tools/rootcanal/model/setup/async_manager.cc @@ -364,6 +364,7 @@ class AsyncManager::AsyncTaskManager { std::chrono::steady_clock::time_point time; bool periodic; std::chrono::milliseconds period{}; + std::mutex in_callback; // Taken when the callback is active TaskCallback callback; AsyncTaskId task_id; AsyncUserId user_id; @@ -381,8 +382,22 @@ class AsyncManager::AsyncTaskManager { if (tasks_by_id_.count(async_task_id) == 0) { return false; } - task_queue_.erase(tasks_by_id_[async_task_id]); - tasks_by_id_.erase(async_task_id); + + // Now make sure we are not running this task. + // 2 cases: + // - This is called from thread_, this means a running + // scheduled task is actually unregistering. All bets are off. + // - Another thread is calling us, let's make sure the task is not active. + if (thread_.get_id() != std::this_thread::get_id()) { + auto task = tasks_by_id_[async_task_id]; + const std::lock_guard lock(task->in_callback); + task_queue_.erase(task); + tasks_by_id_.erase(async_task_id); + } else { + task_queue_.erase(tasks_by_id_[async_task_id]); + tasks_by_id_.erase(async_task_id); + } + return true; } @@ -437,11 +452,12 @@ class AsyncManager::AsyncTaskManager { void ThreadRoutine() { while (running_) { TaskCallback callback; + std::shared_ptr task_p; bool run_it = false; { std::unique_lock guard(internal_mutex_); if (!task_queue_.empty()) { - std::shared_ptr task_p = *(task_queue_.begin()); + task_p = *(task_queue_.begin()); if (task_p->time < std::chrono::steady_clock::now()) { run_it = true; callback = task_p->callback; @@ -458,6 +474,7 @@ class AsyncManager::AsyncTaskManager { } } if (run_it) { + const std::lock_guard lock(task_p->in_callback); callback(); } { diff --git a/tools/rootcanal/model/setup/async_manager.h b/tools/rootcanal/model/setup/async_manager.h index f6603c49423..e82fa376d27 100644 --- a/tools/rootcanal/model/setup/async_manager.h +++ b/tools/rootcanal/model/setup/async_manager.h @@ -77,18 +77,18 @@ class AsyncManager { std::chrono::milliseconds period, const TaskCallback& callback); - // Cancels the/every future occurrence of the action specified by this id. It - // is guaranteed that the associated callback will not be called after this - // method returns (it could be called during the execution of the method). - // The calling thread may block until the scheduling thread acknowledges the - // cancellation. + // Cancels the/every future occurrence of the action specified by this id. + // The following invariants will hold: + // - The task will not be invoked after this method returns + // - If the task is currently running it will block until the task is + // completed, unless cancel is called from the running task. bool CancelAsyncTask(AsyncTaskId async_task_id); - // Cancels the/every future occurrence of the action specified by this id. It - // is guaranteed that the associated callback will not be called after this - // method returns (it could be called during the execution of the method). - // The calling thread may block until the scheduling thread acknowledges the - // cancellation. + // Cancels the/every future occurrence of the action specified by this id. + // The following invariants will hold: + // - The task will not be invoked after this method returns + // - If the task is currently running it will block until the task is + // completed, unless cancel is called from the running task. bool CancelAsyncTasksFromUser(AsyncUserId user_id); // Execs the given code in a synchronized manner. It is guaranteed that code diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc index 37da7553cb9..56d4f98345c 100644 --- a/tools/rootcanal/model/setup/test_model.cc +++ b/tools/rootcanal/model/setup/test_model.cc @@ -52,6 +52,10 @@ TestModel::TestModel( model_user_id_ = get_user_id_(); } +TestModel::~TestModel() { + StopTimer(); +} + void TestModel::SetTimerPeriod(std::chrono::milliseconds new_period) { timer_period_ = new_period; diff --git a/tools/rootcanal/model/setup/test_model.h b/tools/rootcanal/model/setup/test_model.h index 6449b62b14f..62bbb19b28e 100644 --- a/tools/rootcanal/model/setup/test_model.h +++ b/tools/rootcanal/model/setup/test_model.h @@ -49,7 +49,7 @@ class TestModel { std::function cancel, std::function(const std::string&, int, Phy::Type)> connect_to_remote); - ~TestModel() = default; + ~TestModel(); TestModel(TestModel& model) = delete; TestModel& operator=(const TestModel& model) = delete; diff --git a/tools/rootcanal/test/async_manager_unittest.cc b/tools/rootcanal/test/async_manager_unittest.cc index a0a33d4289a..c7a7444284b 100644 --- a/tools/rootcanal/test/async_manager_unittest.cc +++ b/tools/rootcanal/test/async_manager_unittest.cc @@ -33,6 +33,7 @@ #include // for ratio #include // for string #include // for tuple +#include #include "osi/include/osi.h" // for OSI_NO_INTR @@ -123,6 +124,7 @@ class AsyncManagerSocketTest : public ::testing::Test { void SetUp() override { memset(server_buffer_, 0, kBufferSize); + memset(client_buffer_, 0, kBufferSize); socket_fd_ = StartServer(); @@ -137,7 +139,7 @@ class AsyncManagerSocketTest : public ::testing::Test { void TearDown() override { async_manager_.StopWatchingFileDescriptor(socket_fd_); close(socket_fd_); - ASSERT_TRUE(CheckBufferEquals()); + ASSERT_EQ(std::string_view(server_buffer_, kBufferSize), std::string_view(client_buffer_, kBufferSize)); } int ConnectClient() { @@ -215,6 +217,46 @@ TEST_F(AsyncManagerSocketTest, CanUnsubscribeInCallback) { close(socket_cli_fd); } + +TEST_F(AsyncManagerSocketTest, CanUnsubscribeTaskFromWithinTask) { + Event running; + using namespace std::chrono_literals; + async_manager_.ExecAsyncPeriodically(1, 1ms, 2ms, [&running, this]() { + EXPECT_TRUE(async_manager_.CancelAsyncTask(1)) << "We were scheduled, so cancel should return true"; + EXPECT_FALSE(async_manager_.CancelAsyncTask(1)) << "We were not scheduled, so cancel should return false"; + running.set(true); + }); + + EXPECT_TRUE(running.wait_for(10ms)); +} + + +TEST_F(AsyncManagerSocketTest, UnsubScribeWaitsUntilCompletion) { + using namespace std::chrono_literals; + Event running; + bool cancel_done = false; + bool task_complete = false; + async_manager_.ExecAsyncPeriodically(1, 1ms, 2ms, [&running, &cancel_done, &task_complete]() { + // Let the other thread now we are in the callback.. + running.set(true); + // Wee bit of a hack that relies on timing.. + std::this_thread::sleep_for(20ms); + EXPECT_FALSE(cancel_done) << "Task cancellation did not wait for us to complete!"; + task_complete = true; + }); + + EXPECT_TRUE(running.wait_for(10ms)); + auto start = std::chrono::system_clock::now(); + + // There is a 20ms wait.. so we know that this should take some time. + EXPECT_TRUE(async_manager_.CancelAsyncTask(1)) << "We were scheduled, so cancel should return true"; + cancel_done = true; + EXPECT_TRUE(task_complete) << "We managed to cancel a task while it was not yet finished."; + auto end = std::chrono::system_clock::now(); + auto passed_ms = std::chrono::duration_cast(end - start); + EXPECT_GT(passed_ms.count(), 10); +} + TEST_F(AsyncManagerSocketTest, NoEventsAfterUnsubscribe) { // This tests makes sure the AsyncManager never fires an event // after calling StopWatchingFileDescriptor. -- GitLab From b4586b413b2c3fd85dc68fa4018f9fb536611493 Mon Sep 17 00:00:00 2001 From: Myles Watson Date: Tue, 10 May 2022 20:37:08 +0000 Subject: [PATCH 062/998] Revert "RootCanal: Use weak_ptr for Phy, Device" This reverts commit 8e475e84ece370d1205513261402c5aba04490bd. Reason for revert: Hangs the emulator Bug: 227487676 Bug: 241962982 Test: cert/run Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Id6e4630c1f222612a57372f28700a22ac48c37e3 Change-Id: Id6e4630c1f222612a57372f28700a22ac48c37e3 --- tools/rootcanal/model/devices/device.cc | 12 +++----- tools/rootcanal/model/devices/device.h | 2 +- tools/rootcanal/model/setup/test_model.cc | 37 +++++++++-------------- tools/rootcanal/model/setup/test_model.h | 2 +- 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/tools/rootcanal/model/devices/device.cc b/tools/rootcanal/model/devices/device.cc index f5fa8bba7ec..3bcf30b94f7 100644 --- a/tools/rootcanal/model/devices/device.cc +++ b/tools/rootcanal/model/devices/device.cc @@ -31,8 +31,7 @@ void Device::RegisterPhyLayer(std::shared_ptr phy) { } void Device::UnregisterPhyLayers() { - for (auto weak_phy : phy_layers_) { - auto phy = weak_phy.lock(); + for (auto phy : phy_layers_) { if (phy != nullptr) { phy->Unregister(); } @@ -41,8 +40,7 @@ void Device::UnregisterPhyLayers() { } void Device::UnregisterPhyLayer(Phy::Type phy_type, uint32_t factory_id) { - for (auto& weak_phy : phy_layers_) { - auto phy = weak_phy.lock(); + for (auto& phy : phy_layers_) { if (phy != nullptr && phy->IsFactoryId(factory_id) && phy->GetType() == phy_type) { phy->Unregister(); @@ -61,8 +59,7 @@ bool Device::IsAdvertisementAvailable() const { void Device::SendLinkLayerPacket( std::shared_ptr to_send, Phy::Type phy_type) { - for (auto weak_phy : phy_layers_) { - auto phy = weak_phy.lock(); + for (auto phy : phy_layers_) { if (phy != nullptr && phy->GetType() == phy_type) { phy->Send(to_send); } @@ -71,8 +68,7 @@ void Device::SendLinkLayerPacket( void Device::SendLinkLayerPacket(model::packets::LinkLayerPacketView to_send, Phy::Type phy_type) { - for (auto weak_phy : phy_layers_) { - auto phy = weak_phy.lock(); + for (auto phy : phy_layers_) { if (phy != nullptr && phy->GetType() == phy_type) { phy->Send(to_send); } diff --git a/tools/rootcanal/model/devices/device.h b/tools/rootcanal/model/devices/device.h index 541db6da9ba..25d10247dde 100644 --- a/tools/rootcanal/model/devices/device.h +++ b/tools/rootcanal/model/devices/device.h @@ -85,7 +85,7 @@ class Device { void RegisterCloseCallback(std::function); protected: - std::vector> phy_layers_; + std::vector> phy_layers_; std::chrono::steady_clock::time_point last_advertisement_; diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc index 56d4f98345c..470734c242f 100644 --- a/tools/rootcanal/model/setup/test_model.cc +++ b/tools/rootcanal/model/setup/test_model.cc @@ -92,13 +92,13 @@ void TestModel::Del(size_t dev_index) { schedule_task_(model_user_id_, std::chrono::milliseconds(0), [this, dev_index]() { devices_[dev_index]->UnregisterPhyLayers(); - devices_[dev_index].reset(); + devices_[dev_index] = nullptr; }); } size_t TestModel::AddPhy(Phy::Type phy_type) { size_t factory_id = phys_.size(); - phys_.push_back(std::make_shared(phy_type, factory_id)); + phys_.emplace_back(phy_type, factory_id); return factory_id; } @@ -107,11 +107,9 @@ void TestModel::DelPhy(size_t phy_index) { LOG_WARN("Unknown phy at index %zu", phy_index); return; } - schedule_task_(model_user_id_, std::chrono::milliseconds(0), - [this, phy_index]() { - phys_[phy_index]->UnregisterAllPhyLayers(); - phys_[phy_index].reset(); - }); + schedule_task_( + model_user_id_, std::chrono::milliseconds(0), + [this, phy_index]() { phys_[phy_index].UnregisterAllPhyLayers(); }); } void TestModel::AddDeviceToPhy(size_t dev_index, size_t phy_index) { @@ -124,13 +122,9 @@ void TestModel::AddDeviceToPhy(size_t dev_index, size_t phy_index) { return; } auto dev = devices_[dev_index]; - std::weak_ptr weak_dev = dev; - dev->RegisterPhyLayer(phys_[phy_index]->GetPhyLayer( - [weak_dev](model::packets::LinkLayerPacketView packet) { - auto device = weak_dev.lock(); - if (device != nullptr) { - device->IncomingPacket(std::move(packet)); - } + dev->RegisterPhyLayer(phys_[phy_index].GetPhyLayer( + [dev](model::packets::LinkLayerPacketView packet) { + dev->IncomingPacket(std::move(packet)); }, dev_index)); } @@ -147,8 +141,8 @@ void TestModel::DelDeviceFromPhy(size_t dev_index, size_t phy_index) { schedule_task_(model_user_id_, std::chrono::milliseconds(0), [this, dev_index, phy_index]() { devices_[dev_index]->UnregisterPhyLayer( - phys_[phy_index]->GetType(), - phys_[phy_index]->GetFactoryId()); + phys_[phy_index].GetType(), + phys_[phy_index].GetFactoryId()); }); } @@ -160,7 +154,7 @@ void TestModel::AddLinkLayerConnection(std::shared_ptr dev, AsyncUserId user_id = get_user_id_(); for (size_t i = 0; i < phys_.size(); i++) { - if (phy_type == phys_[i]->GetType()) { + if (phy_type == phys_[i].GetType()) { AddDeviceToPhy(index, i); } } @@ -212,7 +206,8 @@ void TestModel::OnConnectionClosed(size_t index, AsyncUserId user_id) { } cancel_tasks_from_user_(user_id); - Del(index); + devices_[index]->UnregisterPhyLayers(); + devices_[index] = nullptr; } void TestModel::SetDeviceAddress(size_t index, Address address) { @@ -237,11 +232,7 @@ const std::string& TestModel::List() { list_string_ += " Phys: \r\n"; for (size_t i = 0; i < phys_.size(); i++) { list_string_ += " " + std::to_string(i) + ":"; - if (phys_[i] == nullptr) { - list_string_ += " deleted \r\n"; - } else { - list_string_ += phys_[i]->ToString() + " \r\n"; - } + list_string_ += phys_[i].ToString() + " \r\n"; } return list_string_; } diff --git a/tools/rootcanal/model/setup/test_model.h b/tools/rootcanal/model/setup/test_model.h index 62bbb19b28e..2171c537a5e 100644 --- a/tools/rootcanal/model/setup/test_model.h +++ b/tools/rootcanal/model/setup/test_model.h @@ -100,7 +100,7 @@ class TestModel { void Reset(); private: - std::vector> phys_; + std::vector phys_; std::vector> devices_; std::string list_string_; -- GitLab From 8826a4003257d6f6232b6ee81b709973ef59d844 Mon Sep 17 00:00:00 2001 From: David Duarte Date: Wed, 1 Jun 2022 12:04:30 +0000 Subject: [PATCH 063/998] RootCanal: Add Link to Link Manager when connection is created We were adding the link when the connection was pending this lead to some cases where a connection was added but never removed Test: atest pts-bot Bug: 241962982 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: Ieca000d483b77687d0fc29c2e5f08accd3190260 Change-Id: Ieca000d483b77687d0fc29c2e5f08accd3190260 --- .../model/controller/link_layer_controller.cc | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc index 127bf74c24b..cb0ce277dc2 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.cc +++ b/tools/rootcanal/model/controller/link_layer_controller.cc @@ -2182,12 +2182,6 @@ void LinkLayerController::IncomingPagePacket( incoming.GetSourceAddress().ToString().c_str()); } -#ifdef ROOTCANAL_LMP - ASSERT(link_manager_add_link(lm_.get(), - reinterpret_cast( - incoming.GetSourceAddress().data()))); -#endif - bluetooth::hci::Address source_address{}; bluetooth::hci::Address::FromString(page.GetSourceAddress().ToString(), source_address); @@ -2226,6 +2220,11 @@ void LinkLayerController::IncomingPageResponsePacket( LOG_WARN("No free handles"); return; } +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_add_link( + lm_.get(), reinterpret_cast(peer.data()))); +#endif /* ROOTCANAL_LMP */ + if (properties_.IsUnmasked(EventCode::CONNECTION_COMPLETE)) { send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create( ErrorCode::SUCCESS, handle, incoming.GetSourceAddress(), @@ -2851,6 +2850,11 @@ void LinkLayerController::MakePeripheralConnection(const Address& addr, LOG_INFO("CreateConnection failed"); return; } +#ifdef ROOTCANAL_LMP + ASSERT(link_manager_add_link( + lm_.get(), reinterpret_cast(addr.data()))); +#endif /* ROOTCANAL_LMP */ + LOG_INFO("CreateConnection returned handle 0x%x", handle); if (properties_.IsUnmasked(EventCode::CONNECTION_COMPLETE)) { send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create( @@ -2894,10 +2898,6 @@ ErrorCode LinkLayerController::CreateConnection(const Address& addr, uint16_t, addr, properties_.GetAuthenticationEnable() == 1)) { return ErrorCode::CONTROLLER_BUSY; } -#ifdef ROOTCANAL_LMP - ASSERT(link_manager_add_link( - lm_.get(), reinterpret_cast(addr.data()))); -#endif SendLinkLayerPacket(model::packets::PageBuilder::Create( properties_.GetAddress(), addr, properties_.GetClassOfDevice(), -- GitLab From 96644962ccf017794bd71a99a431467b3cbd0773 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Wed, 23 Mar 2022 12:25:38 -0700 Subject: [PATCH 064/998] Move from libbacktrace libunwindstack. libbacktrace is deprecated, so use the new unwindstack object that replaces this. Bug: 120606663 Tag: #refactor Test: Manual - Ran bluetooth_stack_with_facade sent SIGSEGV and Test: observed valid backtrace (on host and device). Test: Ran root-canal on host and sent SIGSEGV and observed valid Test: backtrace (host only executable). Change-Id: I5c95627b3f674495e0d9d022d2efff1da5ccefc (cherry picked from commit ae29ca0c2b0b03501d099567e89d776e6b27ab5d) Merged-In: I5c95627b3f674495e0d9d022d2efff1da5ccefc5 Ignore-AOSP-First: Cherry-picked from AOSP Merged-In: I690e23c4eadd7a9e3f33eb246f4458d23eb03df0 Change-Id: I690e23c4eadd7a9e3f33eb246f4458d23eb03df0 --- system/gd/Android.bp | 2 +- system/gd/facade/facade_main.cc | 23 +++++++++------------ tools/rootcanal/Android.bp | 2 +- tools/rootcanal/desktop/root_canal_main.cc | 24 +++++++++------------- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/system/gd/Android.bp b/system/gd/Android.bp index 7cda7a569f2..5564619b7ab 100644 --- a/system/gd/Android.bp +++ b/system/gd/Android.bp @@ -280,12 +280,12 @@ cc_binary { "libbt_shim_ffi", ], shared_libs: [ - "libbacktrace", "libcrypto", "libgrpc++", "libgrpc++_unsecure", "libgrpc_wrap", "libprotobuf-cpp-full", + "libunwindstack", ], target: { android: { diff --git a/system/gd/facade/facade_main.cc b/system/gd/facade/facade_main.cc index a48df00e5a3..da7a8f81662 100644 --- a/system/gd/facade/facade_main.cc +++ b/system/gd/facade/facade_main.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -27,8 +28,7 @@ // clang-format off #include -#include -#include +#include // clang-format on #include "common/init_flags.h" @@ -71,27 +71,24 @@ void interrupt_handler(int signal_number) { struct sigaction new_act = {.sa_handler = interrupt_handler}; bool crash_callback(const void* crash_context, size_t crash_context_size, void* context) { - pid_t tid = BACKTRACE_CURRENT_THREAD; + std::optional tid; if (crash_context_size >= sizeof(google_breakpad::ExceptionHandler::CrashContext)) { auto* ctx = static_cast(crash_context); - tid = ctx->tid; + *tid = ctx->tid; int signal_number = ctx->siginfo.si_signo; LOG_ERROR("Process crashed, signal: %s[%d], tid: %d", strsignal(signal_number), signal_number, ctx->tid); } else { LOG_ERROR("Process crashed, signal: unknown, tid: unknown"); } - std::unique_ptr backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, tid)); - if (backtrace == nullptr) { - LOG_ERROR("Failed to create backtrace object"); - return false; - } - if (!backtrace->Unwind(0)) { - LOG_ERROR("backtrace->Unwind failed"); + unwindstack::AndroidLocalUnwinder unwinder; + unwindstack::AndroidUnwinderData data; + if (!unwinder.Unwind(tid, data)) { + LOG_ERROR("Unwind failed"); return false; } LOG_ERROR("Backtrace:"); - for (size_t i = 0; i < backtrace->NumFrames(); i++) { - LOG_ERROR("%s", backtrace->FormatFrameData(i).c_str()); + for (const auto& frame : data.frames) { + LOG_ERROR("%s", unwinder.FormatFrame(frame).c_str()); } return true; } diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp index 2c62d8dfa45..948b97ddc68 100644 --- a/tools/rootcanal/Android.bp +++ b/tools/rootcanal/Android.bp @@ -175,7 +175,7 @@ cc_binary_host { ], shared_libs: [ "liblog", - "libbacktrace", + "libunwindstack", ], whole_static_libs: [ "libbt-rootcanal", diff --git a/tools/rootcanal/desktop/root_canal_main.cc b/tools/rootcanal/desktop/root_canal_main.cc index 421f1f9fd00..0db8b205e12 100644 --- a/tools/rootcanal/desktop/root_canal_main.cc +++ b/tools/rootcanal/desktop/root_canal_main.cc @@ -13,12 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include -#include #include #include +#include #include +#include #include "model/setup/async_manager.h" #include "net/posix/posix_async_socket_connector.h" @@ -48,32 +48,28 @@ extern "C" const char* __asan_default_options() { bool crash_callback(const void* crash_context, size_t crash_context_size, void* /* context */) { - pid_t tid = BACKTRACE_CURRENT_THREAD; + std::optional tid; if (crash_context_size >= sizeof(google_breakpad::ExceptionHandler::CrashContext)) { auto* ctx = static_cast( crash_context); - tid = ctx->tid; + *tid = ctx->tid; int signal_number = ctx->siginfo.si_signo; LOG_ERROR("Process crashed, signal: %s[%d], tid: %d", strsignal(signal_number), signal_number, ctx->tid); } else { LOG_ERROR("Process crashed, signal: unknown, tid: unknown"); } - std::unique_ptr backtrace( - Backtrace::Create(BACKTRACE_CURRENT_PROCESS, tid)); - if (backtrace == nullptr) { - LOG_ERROR("Failed to create backtrace object"); - return false; - } - if (!backtrace->Unwind(0)) { - LOG_ERROR("backtrace->Unwind failed"); + unwindstack::AndroidLocalUnwinder unwinder; + unwindstack::AndroidUnwinderData data; + if (!unwinder.Unwind(tid, data)) { + LOG_ERROR("Unwind failed"); return false; } LOG_ERROR("Backtrace:"); - for (size_t i = 0; i < backtrace->NumFrames(); i++) { - LOG_ERROR("%s", backtrace->FormatFrameData(i).c_str()); + for (const auto& frame : data.frames) { + LOG_ERROR("%s", unwinder.FormatFrame(frame).c_str()); } return true; } -- GitLab From 856c8b9605b5052b740cd0355abee002e53b6060 Mon Sep 17 00:00:00 2001 From: Omer Osman Date: Thu, 4 Aug 2022 16:26:15 +0000 Subject: [PATCH 065/998] Add support for Opus in BT Java Service This CL adds Opus to the BluetoothCodecConfig, and sets the codec as the default low latency codec. Bug: 226441860 Test: A2dpCodecConfigTest BluetoothCodecConfigTest A2dpStateMachineTest Tag: #feature Ignore-AOSP-First: TM QPR1 Feature Change-Id: Id94a7f8fab1a7f5e0199d757a70ff19010694fff --- android/app/res/values/config.xml | 1 + .../bluetooth/a2dp/A2dpCodecConfig.java | 28 +++++++-- .../bluetooth/a2dp/A2dpStateMachine.java | 10 ++++ .../bluetooth/a2dp/A2dpCodecConfigTest.java | 60 +++++++++++-------- .../bluetooth/a2dp/A2dpStateMachineTest.java | 33 ++++++++++ .../bluetooth/BluetoothCodecConfig.java | 12 +++- .../bluetooth/BluetoothCodecConfigTest.java | 10 +++- 7 files changed, 120 insertions(+), 34 deletions(-) diff --git a/android/app/res/values/config.xml b/android/app/res/values/config.xml index 8e8c755b2b5..5435b62205a 100644 --- a/android/app/res/values/config.xml +++ b/android/app/res/values/config.xml @@ -85,6 +85,7 @@ 4001 5001 6001 + 7001 true diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/android/app/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java index 3084aaa3b9a..0b1c16b2ccd 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/A2dpStateMachine.java b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java index f14ccac6f4b..4fabb836f16 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 @@ -666,6 +669,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/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java index f9a8c451275..f6e4d22e71b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java @@ -44,6 +44,10 @@ import java.util.Arrays; @MediumTest @RunWith(AndroidJUnit4.class) public class A2dpCodecConfigTest { + + // TODO(b/240635097): remove in U + private static final int SOURCE_CODEC_TYPE_OPUS = 6; + private Context mTargetContext; private BluetoothDevice mTestDevice; private A2dpCodecConfig mA2dpCodecConfig; @@ -57,7 +61,7 @@ public class A2dpCodecConfigTest { BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3 + SOURCE_CODEC_TYPE_OPUS // TODO(b/240635097): update in U }; // Not use the default value to make sure it reads from config @@ -67,6 +71,7 @@ public class A2dpCodecConfigTest { private static final int APTX_HD_PRIORITY_DEFAULT = 7001; private static final int LDAC_PRIORITY_DEFAULT = 9001; private static final int LC3_PRIORITY_DEFAULT = 11001; + private static final int OPUS_PRIORITY_DEFAULT = 13001; private static final int PRIORITY_HIGH = 1000000; private static final BluetoothCodecConfig[] sCodecCapabilities = new BluetoothCodecConfig[] { @@ -108,13 +113,11 @@ public class A2dpCodecConfigTest { | BluetoothCodecConfig.BITS_PER_SAMPLE_32, BluetoothCodecConfig.CHANNEL_MODE_STEREO, 0, 0, 0, 0), // Codec-specific fields - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, - LC3_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 - | BluetoothCodecConfig.SAMPLE_RATE_48000, + buildBluetoothCodecConfig(SOURCE_CODEC_TYPE_OPUS, // TODO(b/240635097): update in U + OPUS_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_48000, BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_MONO - | BluetoothCodecConfig.CHANNEL_MODE_STEREO, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, 0, 0, 0, 0) // Codec-specific fields }; @@ -149,8 +152,8 @@ public class A2dpCodecConfigTest { BluetoothCodecConfig.BITS_PER_SAMPLE_32, BluetoothCodecConfig.CHANNEL_MODE_STEREO, 0, 0, 0, 0), // Codec-specific fields - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, - LC3_PRIORITY_DEFAULT, + buildBluetoothCodecConfig(SOURCE_CODEC_TYPE_OPUS, // TODO(b/240635097): update in U + OPUS_PRIORITY_DEFAULT, BluetoothCodecConfig.SAMPLE_RATE_48000, BluetoothCodecConfig.BITS_PER_SAMPLE_16, BluetoothCodecConfig.CHANNEL_MODE_STEREO, @@ -174,8 +177,8 @@ public class A2dpCodecConfigTest { .thenReturn(APTX_HD_PRIORITY_DEFAULT); when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_ldac)) .thenReturn(LDAC_PRIORITY_DEFAULT); - when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_lc3)) - .thenReturn(LC3_PRIORITY_DEFAULT); + when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_opus)) + .thenReturn(OPUS_PRIORITY_DEFAULT); mA2dpCodecConfig = new A2dpCodecConfig(mMockContext, mA2dpNativeInterface); mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); @@ -209,8 +212,8 @@ public class A2dpCodecConfigTest { case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT); break; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3: - Assert.assertEquals(config.getCodecPriority(), LC3_PRIORITY_DEFAULT); + case SOURCE_CODEC_TYPE_OPUS: // TODO(b/240635097): update in U + Assert.assertEquals(config.getCodecPriority(), OPUS_PRIORITY_DEFAULT); break; } } @@ -242,8 +245,9 @@ public class A2dpCodecConfigTest { BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH, true); testCodecPriorityChangeHelper( - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, PRIORITY_HIGH, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, + SOURCE_CODEC_TYPE_OPUS, PRIORITY_HIGH, false); } @@ -255,27 +259,33 @@ public class A2dpCodecConfigTest { public void testSetCodecPreference_priorityDefaultToRaiseHigh() { testCodecPriorityChangeHelper( BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, true); testCodecPriorityChangeHelper( BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, true); testCodecPriorityChangeHelper( BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, true); testCodecPriorityChangeHelper( BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, true); testCodecPriorityChangeHelper( BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, true); testCodecPriorityChangeHelper( - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, PRIORITY_HIGH, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, LC3_PRIORITY_DEFAULT, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, PRIORITY_HIGH, + SOURCE_CODEC_TYPE_OPUS, OPUS_PRIORITY_DEFAULT, false); } @@ -302,7 +312,8 @@ public class A2dpCodecConfigTest { BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH, true); testCodecPriorityChangeHelper( - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, PRIORITY_HIGH, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, PRIORITY_HIGH, BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH, true); } @@ -330,7 +341,8 @@ public class A2dpCodecConfigTest { BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH, true); testCodecPriorityChangeHelper( - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, PRIORITY_HIGH, + // TODO(b/240635097): update in U + SOURCE_CODEC_TYPE_OPUS, PRIORITY_HIGH, BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH, true); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java index f2a81dd39c9..37444461098 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java @@ -53,6 +53,9 @@ import java.util.Arrays; @MediumTest @RunWith(AndroidJUnit4.class) public class A2dpStateMachineTest { + // TODO(b/240635097): remove in U + private static final int SOURCE_CODEC_TYPE_OPUS = 6; + private BluetoothAdapter mAdapter; private Context mTargetContext; private HandlerThread mHandlerThread; @@ -62,6 +65,7 @@ public class A2dpStateMachineTest { private BluetoothCodecConfig mCodecConfigSbc; private BluetoothCodecConfig mCodecConfigAac; + private BluetoothCodecConfig mCodecConfigOpus; @Mock private AdapterService mAdapterService; @Mock private A2dpService mA2dpService; @@ -103,6 +107,18 @@ public class A2dpStateMachineTest { .setCodecSpecific4(0) .build(); + mCodecConfigOpus = new BluetoothCodecConfig.Builder() + .setCodecType(SOURCE_CODEC_TYPE_OPUS) // TODO(b/240635097): update in U + .setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT) + .setSampleRate(BluetoothCodecConfig.SAMPLE_RATE_48000) + .setBitsPerSample(BluetoothCodecConfig.BITS_PER_SAMPLE_16) + .setChannelMode(BluetoothCodecConfig.CHANNEL_MODE_STEREO) + .setCodecSpecific1(0) + .setCodecSpecific2(0) + .setCodecSpecific3(0) + .setCodecSpecific4(0) + .build(); + // Set up thread and looper mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread"); mHandlerThread.start(); @@ -326,12 +342,21 @@ public class A2dpStateMachineTest { codecsSelectableSbcAac[0] = mCodecConfigSbc; codecsSelectableSbcAac[1] = mCodecConfigAac; + BluetoothCodecConfig[] codecsSelectableSbcAacOpus; + codecsSelectableSbcAacOpus = new BluetoothCodecConfig[3]; + codecsSelectableSbcAacOpus[0] = mCodecConfigSbc; + codecsSelectableSbcAacOpus[1] = mCodecConfigAac; + codecsSelectableSbcAacOpus[2] = mCodecConfigOpus; + BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc, Arrays.asList(codecsSelectableSbcAac), Arrays.asList(codecsSelectableSbc)); BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc, Arrays.asList(codecsSelectableSbcAac), Arrays.asList(codecsSelectableSbcAac)); BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac, Arrays.asList(codecsSelectableSbcAac), Arrays.asList(codecsSelectableSbcAac)); + BluetoothCodecStatus codecStatusOpusAndSbcAacOpus = new BluetoothCodecStatus( + mCodecConfigOpus, Arrays.asList(codecsSelectableSbcAacOpus), + Arrays.asList(codecsSelectableSbcAacOpus)); // Set default codec status when device disconnected // Selected codec = SBC, selectable codec = SBC @@ -368,5 +393,13 @@ public class A2dpStateMachineTest { mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac); verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false); verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice); + + // Update selected codec + // Selected codec = OPUS, selectable codec = SBC+AAC+OPUS + mA2dpStateMachine.processCodecConfigEvent(codecStatusOpusAndSbcAacOpus); + if (!offloadEnabled) { + verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusOpusAndSbcAacOpus, true); + } + verify(mA2dpService, times(3)).updateOptionalCodecsSupport(mTestDevice); } } diff --git a/framework/java/android/bluetooth/BluetoothCodecConfig.java b/framework/java/android/bluetooth/BluetoothCodecConfig.java index 9fc9fb34111..cd0ffe40942 100644 --- a/framework/java/android/bluetooth/BluetoothCodecConfig.java +++ b/framework/java/android/bluetooth/BluetoothCodecConfig.java @@ -76,6 +76,11 @@ public final class BluetoothCodecConfig implements Parcelable { */ public static final int SOURCE_CODEC_TYPE_LC3 = 5; + /** + * Source codec type Opus. + */ + private static final int SOURCE_CODEC_TYPE_OPUS = 6; + /** * Source codec type invalid. This is the default value used for codec * type. @@ -85,7 +90,7 @@ public final class BluetoothCodecConfig implements Parcelable { /** * Represents the count of valid source codec types. */ - private static final int SOURCE_CODEC_TYPE_MAX = 6; + private static final int SOURCE_CODEC_TYPE_MAX = 7; /** @hide */ @IntDef(prefix = "CODEC_PRIORITY_", value = { @@ -462,7 +467,9 @@ public final class BluetoothCodecConfig implements Parcelable { case SOURCE_CODEC_TYPE_LDAC: return "LDAC"; case SOURCE_CODEC_TYPE_LC3: - return "LC3"; + return "LC3"; + case SOURCE_CODEC_TYPE_OPUS: + return "Opus"; case SOURCE_CODEC_TYPE_INVALID: return "INVALID CODEC"; default: @@ -712,6 +719,7 @@ public final class BluetoothCodecConfig implements Parcelable { case SOURCE_CODEC_TYPE_AAC: case SOURCE_CODEC_TYPE_LDAC: case SOURCE_CODEC_TYPE_LC3: + case SOURCE_CODEC_TYPE_OPUS: if (mCodecSpecific1 != other.mCodecSpecific1) { return false; } diff --git a/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java index cb059aeb430..19bd3f09c4e 100644 --- a/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java +++ b/framework/tests/unit/src/android/bluetooth/BluetoothCodecConfigTest.java @@ -24,13 +24,16 @@ import junit.framework.TestCase; * Unit test cases for {@link BluetoothCodecConfig}. */ public class BluetoothCodecConfigTest extends TestCase { + // TODO(b/240635097): remove in U + private static final int SOURCE_CODEC_TYPE_OPUS = 6; + private static final int[] sCodecTypeArray = new int[] { BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + SOURCE_CODEC_TYPE_OPUS, // TODO(b/240635097): update in U BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, }; private static final int[] sCodecPriorityArray = new int[] { @@ -213,8 +216,9 @@ public class BluetoothCodecConfigTest extends TestCase { if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { assertEquals("LDAC", BluetoothCodecConfig.getCodecName(codec_type)); } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { - assertEquals("LC3", BluetoothCodecConfig.getCodecName(codec_type)); + // TODO(b/240635097): update in U + if (codec_type == SOURCE_CODEC_TYPE_OPUS) { + assertEquals("Opus", BluetoothCodecConfig.getCodecName(codec_type)); } if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { assertEquals("INVALID CODEC", BluetoothCodecConfig.getCodecName(codec_type)); -- GitLab From 29d38ad3c2c2593adaf9a61290d1d4380d46fc39 Mon Sep 17 00:00:00 2001 From: Omer Osman Date: Thu, 4 Aug 2022 23:48:53 +0000 Subject: [PATCH 066/998] Add support for Opus in BT Audio HAL Provide support for software codec and DSP offload Bug: 226441860 Test: Verification using bds-dev sink device Tag: #feature Ignore-AOSP-First: TM QPR1 Feature Change-Id: I3e771473294ab59cc6e9513835ce0d54b72a8f84 --- .../aidl/a2dp_encoding_aidl.cc | 9 ++- .../aidl/codec_status_aidl.cc | 79 ++++++++++++++++++- .../aidl/codec_status_aidl.h | 4 +- system/bta/av/bta_av_aact.cc | 3 + system/bta/include/bta_av_api.h | 3 +- system/include/hardware/bt_av.h | 20 +++++ system/stack/a2dp/a2dp_vendor.cc | 14 ++++ system/stack/test/stack_a2dp_test.cc | 5 ++ 8 files changed, 133 insertions(+), 4 deletions(-) diff --git a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc index 72cfd35ce54..0408f7a47e6 100644 --- a/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc +++ b/system/audio_hal_interface/aidl/a2dp_encoding_aidl.cc @@ -44,6 +44,7 @@ using ::bluetooth::audio::aidl::codec::A2dpCodecToHalBitsPerSample; using ::bluetooth::audio::aidl::codec::A2dpCodecToHalChannelMode; using ::bluetooth::audio::aidl::codec::A2dpCodecToHalSampleRate; using ::bluetooth::audio::aidl::codec::A2dpLdacToHalConfig; +using ::bluetooth::audio::aidl::codec::A2dpOpusToHalConfig; using ::bluetooth::audio::aidl::codec::A2dpSbcToHalConfig; /*** @@ -270,6 +271,12 @@ bool a2dp_get_selected_hal_codec_config(CodecConfiguration* codec_config) { } break; } + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: { + if (!A2dpOpusToHalConfig(codec_config, a2dp_config)) { + return false; + } + break; + } case BTAV_A2DP_CODEC_INDEX_MAX: [[fallthrough]]; default: @@ -570,4 +577,4 @@ void set_low_latency_mode_allowed(bool allowed) { } // namespace a2dp } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/codec_status_aidl.cc b/system/audio_hal_interface/aidl/codec_status_aidl.cc index 6e63fb3929d..ec62ed75ffd 100644 --- a/system/audio_hal_interface/aidl/codec_status_aidl.cc +++ b/system/audio_hal_interface/aidl/codec_status_aidl.cc @@ -46,6 +46,8 @@ using ::aidl::android::hardware::bluetooth::audio::LdacCapabilities; using ::aidl::android::hardware::bluetooth::audio::LdacChannelMode; using ::aidl::android::hardware::bluetooth::audio::LdacConfiguration; using ::aidl::android::hardware::bluetooth::audio::LdacQualityIndex; +using ::aidl::android::hardware::bluetooth::audio::OpusCapabilities; +using ::aidl::android::hardware::bluetooth::audio::OpusConfiguration; using ::aidl::android::hardware::bluetooth::audio::SbcAllocMethod; using ::aidl::android::hardware::bluetooth::audio::SbcCapabilities; using ::aidl::android::hardware::bluetooth::audio::SbcChannelMode; @@ -144,6 +146,25 @@ bool ldac_offloading_capability_match(const LdacCapabilities& ldac_capability, << " capability=" << ldac_capability.toString(); return true; } + +bool opus_offloading_capability_match( + const std::optional& opus_capability, + const std::optional& opus_config) { + if (!ContainedInVector(opus_capability->channelMode, + opus_config->channelMode) || + !ContainedInVector(opus_capability->frameDurationUs, + opus_config->frameDurationUs) || + !ContainedInVector(opus_capability->samplingFrequencyHz, + opus_config->samplingFrequencyHz)) { + LOG(WARNING) << __func__ << ": software codec=" << opus_config->toString() + << " capability=" << opus_capability->toString(); + return false; + } + LOG(INFO) << __func__ << ": offloading codec=" << opus_config->toString() + << " capability=" << opus_capability->toString(); + return true; +} + } // namespace const CodecConfiguration kInvalidCodecConfiguration = {}; @@ -453,6 +474,50 @@ bool A2dpLdacToHalConfig(CodecConfiguration* codec_config, return true; } +bool A2dpOpusToHalConfig(CodecConfiguration* codec_config, + A2dpCodecConfig* a2dp_config) { + btav_a2dp_codec_config_t current_codec = a2dp_config->getCodecConfig(); + if (current_codec.codec_type != BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS) { + codec_config = {}; + return false; + } + tBT_A2DP_OFFLOAD a2dp_offload; + a2dp_config->getCodecSpecificConfig(&a2dp_offload); + codec_config->codecType = CodecType::OPUS; + OpusConfiguration opus_config = {}; + + opus_config.pcmBitDepth = A2dpCodecToHalBitsPerSample(current_codec); + if (opus_config.pcmBitDepth <= 0) { + LOG(ERROR) << __func__ << ": Unknown Opus bits_per_sample=" + << current_codec.bits_per_sample; + return false; + } + opus_config.samplingFrequencyHz = A2dpCodecToHalSampleRate(current_codec); + if (opus_config.samplingFrequencyHz <= 0) { + LOG(ERROR) << __func__ + << ": Unknown Opus sample_rate=" << current_codec.sample_rate; + return false; + } + opus_config.channelMode = A2dpCodecToHalChannelMode(current_codec); + if (opus_config.channelMode == ChannelMode::UNKNOWN) { + LOG(ERROR) << __func__ + << ": Unknown Opus channel_mode=" << current_codec.channel_mode; + return false; + } + + opus_config.frameDurationUs = 20000; + + if (opus_config.channelMode == ChannelMode::STEREO) { + opus_config.octetsPerFrame = 640; + } else { + opus_config.octetsPerFrame = 320; + } + + codec_config->config.set( + opus_config); + return true; +} + bool UpdateOffloadingCapabilities( const std::vector& framework_preference) { audio_hal_capabilities = @@ -476,6 +541,9 @@ bool UpdateOffloadingCapabilities( case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC: codec_type_set.insert(CodecType::LDAC); break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + codec_type_set.insert(CodecType::OPUS); + break; case BTAV_A2DP_CODEC_INDEX_SINK_SBC: [[fallthrough]]; case BTAV_A2DP_CODEC_INDEX_SINK_AAC: @@ -560,6 +628,15 @@ bool IsCodecOffloadingEnabled(const CodecConfiguration& codec_config) { .get(); return ldac_offloading_capability_match(ldac_capability, ldac_config); } + case CodecType::OPUS: { + std::optional opus_capability = + codec_capability.capabilities + .get(); + std::optional opus_config = + codec_config.config + .get(); + return opus_offloading_capability_match(opus_capability, opus_config); + } case CodecType::UNKNOWN: [[fallthrough]]; default: @@ -575,4 +652,4 @@ bool IsCodecOffloadingEnabled(const CodecConfiguration& codec_config) { } // namespace codec } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/codec_status_aidl.h b/system/audio_hal_interface/aidl/codec_status_aidl.h index c2fa8191b1a..3caac07aac1 100644 --- a/system/audio_hal_interface/aidl/codec_status_aidl.h +++ b/system/audio_hal_interface/aidl/codec_status_aidl.h @@ -46,6 +46,8 @@ bool A2dpAptxToHalConfig(CodecConfiguration* codec_config, A2dpCodecConfig* a2dp_config); bool A2dpLdacToHalConfig(CodecConfiguration* codec_config, A2dpCodecConfig* a2dp_config); +bool A2dpOpusToHalConfig(CodecConfiguration* codec_config, + A2dpCodecConfig* a2dp_config); bool UpdateOffloadingCapabilities( const std::vector& framework_preference); @@ -59,4 +61,4 @@ bool IsCodecOffloadingEnabled(const CodecConfiguration& codec_config); } // namespace codec } // namespace aidl } // namespace audio -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth diff --git a/system/bta/av/bta_av_aact.cc b/system/bta/av/bta_av_aact.cc index 9e48782172b..d2885e8666c 100644 --- a/system/bta/av/bta_av_aact.cc +++ b/system/bta/av/bta_av_aact.cc @@ -3208,6 +3208,9 @@ static void bta_av_offload_codec_builder(tBTA_AV_SCB* p_scb, case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC: codec_type = BTA_AV_CODEC_TYPE_LDAC; break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + codec_type = BTA_AV_CODEC_TYPE_OPUS; + break; default: APPL_TRACE_ERROR("%s: Unknown Codec type ", __func__); return; diff --git a/system/bta/include/bta_av_api.h b/system/bta/include/bta_av_api.h index fd1dd000df9..143e52ac9e6 100644 --- a/system/bta/include/bta_av_api.h +++ b/system/bta/include/bta_av_api.h @@ -154,7 +154,8 @@ typedef enum { BTA_AV_CODEC_TYPE_AAC = 0x02, BTA_AV_CODEC_TYPE_APTX = 0x04, BTA_AV_CODEC_TYPE_APTXHD = 0x08, - BTA_AV_CODEC_TYPE_LDAC = 0x10 + BTA_AV_CODEC_TYPE_LDAC = 0x10, + BTA_AV_CODEC_TYPE_OPUS = 0x20 } tBTA_AV_CODEC_TYPE; /* Event associated with BTA_AV_ENABLE_EVT */ diff --git a/system/include/hardware/bt_av.h b/system/include/hardware/bt_av.h index 8a7234b9df0..61dbe50ff5f 100644 --- a/system/include/hardware/bt_av.h +++ b/system/include/hardware/bt_av.h @@ -55,6 +55,8 @@ typedef enum { BTAV_A2DP_CODEC_INDEX_SOURCE_APTX, BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD, BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC, + BTAV_A2DP_CODEC_INDEX_SOURCE_LC3, + BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS, BTAV_A2DP_CODEC_INDEX_SOURCE_MAX, @@ -64,6 +66,7 @@ typedef enum { BTAV_A2DP_CODEC_INDEX_SINK_SBC = BTAV_A2DP_CODEC_INDEX_SINK_MIN, BTAV_A2DP_CODEC_INDEX_SINK_AAC, BTAV_A2DP_CODEC_INDEX_SINK_LDAC, + BTAV_A2DP_CODEC_INDEX_SINK_OPUS, BTAV_A2DP_CODEC_INDEX_SINK_MAX, @@ -96,6 +99,14 @@ typedef enum { BTAV_A2DP_CODEC_SAMPLE_RATE_24000 = 0x1 << 7 } btav_a2dp_codec_sample_rate_t; +typedef enum { + BTAV_A2DP_CODEC_FRAME_SIZE_NONE = 0x0, + BTAV_A2DP_CODEC_FRAME_SIZE_20MS = 0x1 << 0, + BTAV_A2DP_CODEC_FRAME_SIZE_15MS = 0x1 << 1, + BTAV_A2DP_CODEC_FRAME_SIZE_10MS = 0x1 << 2, + BTAV_A2DP_CODEC_FRAME_SIZE_75MS = 0x1 << 3, +} btav_a2dp_codec_frame_size_t; + typedef enum { BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE = 0x0, BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 = 0x1 << 0, @@ -164,6 +175,15 @@ typedef struct { case BTAV_A2DP_CODEC_INDEX_SINK_LDAC: codec_name_str = "LDAC (Sink)"; break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: + codec_name_str = "LC3"; + break; + case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: + codec_name_str = "Opus (Sink)"; + break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + codec_name_str = "Opus"; + break; case BTAV_A2DP_CODEC_INDEX_MAX: codec_name_str = "Unknown(CODEC_INDEX_MAX)"; break; diff --git a/system/stack/a2dp/a2dp_vendor.cc b/system/stack/a2dp/a2dp_vendor.cc index 759fc431347..30dc2069f89 100644 --- a/system/stack/a2dp/a2dp_vendor.cc +++ b/system/stack/a2dp/a2dp_vendor.cc @@ -601,6 +601,14 @@ const char* A2DP_VendorCodecIndexStr(btav_a2dp_codec_index_t codec_index) { return A2DP_VendorCodecIndexStrLdac(); case BTAV_A2DP_CODEC_INDEX_SINK_LDAC: return A2DP_VendorCodecIndexStrLdacSink(); + case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: + return "LC3 not implemented"; + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + // TODO(b/226441860): in-progress + return "Opus"; + case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: + // TODO(b/226441860): in-progress + return "Opus SINK"; // Add a switch statement for each vendor-specific codec case BTAV_A2DP_CODEC_INDEX_MAX: break; @@ -626,6 +634,12 @@ bool A2DP_VendorInitCodecConfig(btav_a2dp_codec_index_t codec_index, return A2DP_VendorInitCodecConfigLdac(p_cfg); case BTAV_A2DP_CODEC_INDEX_SINK_LDAC: return A2DP_VendorInitCodecConfigLdacSink(p_cfg); + case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: + break; // not implemented + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + // TODO(b/226441860): in-progress + case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: + // TODO(b/226441860): in-progress // Add a switch statement for each vendor-specific codec case BTAV_A2DP_CODEC_INDEX_MAX: break; diff --git a/system/stack/test/stack_a2dp_test.cc b/system/stack/test/stack_a2dp_test.cc index 71526e7c3ad..fe7692a8a5f 100644 --- a/system/stack/test/stack_a2dp_test.cc +++ b/system/stack/test/stack_a2dp_test.cc @@ -264,6 +264,11 @@ class StackA2dpTest : public ::testing::Test { // shared library installed. supported = has_shared_library(LDAC_DECODER_LIB_NAME); break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + // TODO(b/226441860): in-progress + case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: + // TODO(b/226441860): in-progress case BTAV_A2DP_CODEC_INDEX_MAX: // Needed to avoid using "default:" case so we can capture when // a new codec is added, and it can be included here. -- GitLab From a00df11d9881f6a19e510a652445cbe320d83717 Mon Sep 17 00:00:00 2001 From: Omer Osman Date: Mon, 8 Aug 2022 04:06:09 +0000 Subject: [PATCH 067/998] Add support for Opus over A2DP This CL provides support for IETF RFC 2012-6716 Opus as a BT A2DP codec for source or sink devices to the AOSP BT stack. During AVDTP handshake for A2DP, codec capabilities are negotiated between the source and sink devices, and the connection is established accordingly. The CL provides support for Opus codec selection through Developer Options. The Opus codec provides a highly optimized perceptual coding implementation, with the following core features: * Hybrid, Music (CELT) and Speech (SILK) modes * Scalable Bitrate from 6 - 510 kbps * Framesizes from 2.5 - 60ms * Support for Packet Loss Concealment * High efficiency coding with 3 byte frames to encode silence The core function calls for the codec are: opus_encode (OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes) opus_decode (OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec) The majority of this CL implements the addition of the vendor codec in the A2DP stack, interfacing of the audio through the A2DP HAL, and codec capability negotiation over AVDTP. Bug: 226441860 Test: atest net_test_stack:StackA2dpTest and bds-dev sink Tag: #feature Ignore-AOSP-First: TM QPR1 Feature Change-Id: I05ecf07ab01f8045166b3d2da60fc2315c743cd2 --- system/audio_hal_interface/fuzzer/Android.bp | 1 + system/btif/Android.bp | 1 + system/build/Android.bp | 1 + system/gd/rust/topshim/facade/Android.bp | 1 + system/service/Android.bp | 2 + system/stack/Android.bp | 5 + system/stack/a2dp/a2dp_codec_config.cc | 12 +- system/stack/a2dp/a2dp_vendor.cc | 128 +- system/stack/a2dp/a2dp_vendor_opus.cc | 1332 +++++++++++++++++ system/stack/a2dp/a2dp_vendor_opus_decoder.cc | 159 ++ system/stack/a2dp/a2dp_vendor_opus_encoder.cc | 532 +++++++ system/stack/include/a2dp_error_codes.h | 3 + system/stack/include/a2dp_vendor_opus.h | 255 ++++ .../include/a2dp_vendor_opus_constants.h | 66 + .../stack/include/a2dp_vendor_opus_decoder.h | 45 + .../stack/include/a2dp_vendor_opus_encoder.h | 59 + system/stack/test/fuzzers/Android.bp | 1 + system/stack/test/stack_a2dp_test.cc | 125 +- system/test/headless/Android.bp | 1 + system/test/suite/Android.bp | 1 + 20 files changed, 2720 insertions(+), 10 deletions(-) create mode 100644 system/stack/a2dp/a2dp_vendor_opus.cc create mode 100644 system/stack/a2dp/a2dp_vendor_opus_decoder.cc create mode 100644 system/stack/a2dp/a2dp_vendor_opus_encoder.cc create mode 100644 system/stack/include/a2dp_vendor_opus.h create mode 100644 system/stack/include/a2dp_vendor_opus_constants.h create mode 100644 system/stack/include/a2dp_vendor_opus_decoder.h create mode 100644 system/stack/include/a2dp_vendor_opus_encoder.h diff --git a/system/audio_hal_interface/fuzzer/Android.bp b/system/audio_hal_interface/fuzzer/Android.bp index ac74f1382fc..844fdd35846 100644 --- a/system/audio_hal_interface/fuzzer/Android.bp +++ b/system/audio_hal_interface/fuzzer/Android.bp @@ -72,6 +72,7 @@ cc_defaults { "libudrv-uipc", "libbt-common", "liblc3", + "libopus", "libstatslog_bt", "libvndksupport", "libprocessgroup", diff --git a/system/btif/Android.bp b/system/btif/Android.bp index 079d12cc04e..0a43f060177 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -238,6 +238,7 @@ cc_test { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libosi", "libudrv-uipc", ], diff --git a/system/build/Android.bp b/system/build/Android.bp index 9a0c996a7d1..79f7d65632e 100644 --- a/system/build/Android.bp +++ b/system/build/Android.bp @@ -207,6 +207,7 @@ fluoride_defaults { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libprotobuf-cpp-lite", "libstatslog_bt", "libudrv-uipc", diff --git a/system/gd/rust/topshim/facade/Android.bp b/system/gd/rust/topshim/facade/Android.bp index 5d2b06b2ca7..058b781b3bc 100644 --- a/system/gd/rust/topshim/facade/Android.bp +++ b/system/gd/rust/topshim/facade/Android.bp @@ -55,6 +55,7 @@ rust_binary_host { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libudrv-uipc", "libbluetooth_gd", // Gabeldorsche "libbluetooth-dumpsys", diff --git a/system/service/Android.bp b/system/service/Android.bp index 91dcff058dd..560ab2b8330 100644 --- a/system/service/Android.bp +++ b/system/service/Android.bp @@ -119,6 +119,7 @@ cc_binary { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libosi", "libudrv-uipc", ], @@ -202,6 +203,7 @@ cc_test { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libosi", "libudrv-uipc", ], diff --git a/system/stack/Android.bp b/system/stack/Android.bp index 8d1b8e54472..94e0f4ea80f 100644 --- a/system/stack/Android.bp +++ b/system/stack/Android.bp @@ -55,6 +55,7 @@ cc_library_static { "external/aac/libSYS/include", "external/libldac/inc", "external/libldac/abr/inc", + "external/libopus/include", "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/gd", "packages/modules/Bluetooth/system/vnd/include", @@ -85,6 +86,9 @@ cc_library_static { "a2dp/a2dp_vendor_ldac.cc", "a2dp/a2dp_vendor_ldac_decoder.cc", "a2dp/a2dp_vendor_ldac_encoder.cc", + "a2dp/a2dp_vendor_opus.cc", + "a2dp/a2dp_vendor_opus_encoder.cc", + "a2dp/a2dp_vendor_opus_decoder.cc", "avct/avct_api.cc", "avct/avct_bcb_act.cc", "avct/avct_ccb.cc", @@ -268,6 +272,7 @@ cc_test { "libbtdevice", "libg722codec", "liblc3", + "libopus", "libosi", "libudrv-uipc", "libbt-protos-lite", diff --git a/system/stack/a2dp/a2dp_codec_config.cc b/system/stack/a2dp/a2dp_codec_config.cc index 52e0f6fcb19..b7b970fbe48 100644 --- a/system/stack/a2dp/a2dp_codec_config.cc +++ b/system/stack/a2dp/a2dp_codec_config.cc @@ -33,6 +33,7 @@ #include "a2dp_vendor_aptx.h" #include "a2dp_vendor_aptx_hd.h" #include "a2dp_vendor_ldac.h" +#include "a2dp_vendor_opus.h" #endif #include "bta/av/bta_av_int.h" @@ -111,7 +112,7 @@ void A2dpCodecConfig::setDefaultCodecPriority() { A2dpCodecConfig* A2dpCodecConfig::createCodec( btav_a2dp_codec_index_t codec_index, btav_a2dp_codec_priority_t codec_priority) { - LOG_INFO("%s: codec %s", __func__, A2DP_CodecIndexStr(codec_index)); + LOG_INFO("%s", A2DP_CodecIndexStr(codec_index)); A2dpCodecConfig* codec_config = nullptr; switch (codec_index) { @@ -140,6 +141,12 @@ A2dpCodecConfig* A2dpCodecConfig::createCodec( case BTAV_A2DP_CODEC_INDEX_SINK_LDAC: codec_config = new A2dpCodecConfigLdacSink(codec_priority); break; + case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: + codec_config = new A2dpCodecConfigOpusSource(codec_priority); + break; + case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: + codec_config = new A2dpCodecConfigOpusSink(codec_priority); + break; #endif case BTAV_A2DP_CODEC_INDEX_MAX: default: @@ -586,6 +593,9 @@ bool A2dpCodecs::init() { } else if (strcmp(tok, "ldac") == 0) { LOG_INFO("%s: LDAC offload supported", __func__); offload_codec_support[BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC] = true; + } else if (strcmp(tok, "opus") == 0) { + LOG_INFO("%s: Opus offload supported", __func__); + offload_codec_support[BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS] = true; #endif } tok = strtok_r(NULL, "-", &tmp_token); diff --git a/system/stack/a2dp/a2dp_vendor.cc b/system/stack/a2dp/a2dp_vendor.cc index 30dc2069f89..cde70c1b598 100644 --- a/system/stack/a2dp/a2dp_vendor.cc +++ b/system/stack/a2dp/a2dp_vendor.cc @@ -25,6 +25,7 @@ #include "a2dp_vendor_aptx.h" #include "a2dp_vendor_aptx_hd.h" #include "a2dp_vendor_ldac.h" +#include "a2dp_vendor_opus.h" #include "bt_target.h" #include "osi/include/log.h" #include "osi/include/osi.h" @@ -51,6 +52,11 @@ bool A2DP_IsVendorSourceCodecValid(const uint8_t* p_codec_info) { return A2DP_IsVendorSourceCodecValidLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsVendorSourceCodecValidOpus(p_codec_info); + } + // Add checks based on return false; @@ -68,6 +74,11 @@ bool A2DP_IsVendorSinkCodecValid(const uint8_t* p_codec_info) { return A2DP_IsVendorSinkCodecValidLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsVendorSinkCodecValidOpus(p_codec_info); + } + return false; } @@ -83,6 +94,11 @@ bool A2DP_IsVendorPeerSourceCodecValid(const uint8_t* p_codec_info) { return A2DP_IsVendorPeerSourceCodecValidLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsVendorPeerSourceCodecValidOpus(p_codec_info); + } + return false; } @@ -107,6 +123,11 @@ bool A2DP_IsVendorPeerSinkCodecValid(const uint8_t* p_codec_info) { return A2DP_IsVendorPeerSinkCodecValidLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsVendorPeerSinkCodecValidOpus(p_codec_info); + } + // Add checks based on return false; @@ -124,6 +145,11 @@ bool A2DP_IsVendorSinkCodecSupported(const uint8_t* p_codec_info) { return A2DP_IsVendorSinkCodecSupportedLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsVendorSinkCodecSupportedOpus(p_codec_info); + } + return false; } @@ -139,6 +165,11 @@ bool A2DP_IsVendorPeerSourceCodecSupported(const uint8_t* p_codec_info) { return A2DP_IsPeerSourceCodecSupportedLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_IsPeerSourceCodecSupportedOpus(p_codec_info); + } + return false; } @@ -185,6 +216,12 @@ bool A2DP_VendorUsesRtpHeader(bool content_protection_enabled, p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorUsesRtpHeaderOpus(content_protection_enabled, + p_codec_info); + } + // Add checks based on return true; @@ -211,6 +248,11 @@ const char* A2DP_VendorCodecName(const uint8_t* p_codec_info) { return A2DP_VendorCodecNameLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorCodecNameOpus(p_codec_info); + } + // Add checks based on return "UNKNOWN VENDOR CODEC"; @@ -250,6 +292,11 @@ bool A2DP_VendorCodecTypeEquals(const uint8_t* p_codec_info_a, return A2DP_VendorCodecTypeEqualsLdac(p_codec_info_a, p_codec_info_b); } + // Check for Opus + if (vendor_id_a == A2DP_OPUS_VENDOR_ID && codec_id_a == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorCodecTypeEqualsOpus(p_codec_info_a, p_codec_info_b); + } + // OPTIONAL: Add extra vendor-specific checks based on the // vendor-specific data stored in "p_codec_info_a" and "p_codec_info_b". @@ -290,6 +337,11 @@ bool A2DP_VendorCodecEquals(const uint8_t* p_codec_info_a, return A2DP_VendorCodecEqualsLdac(p_codec_info_a, p_codec_info_b); } + // Check for Opus + if (vendor_id_a == A2DP_OPUS_VENDOR_ID && codec_id_a == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorCodecEqualsOpus(p_codec_info_a, p_codec_info_b); + } + // Add extra vendor-specific checks based on the // vendor-specific data stored in "p_codec_info_a" and "p_codec_info_b". @@ -317,6 +369,11 @@ int A2DP_VendorGetBitRate(const uint8_t* p_codec_info) { return A2DP_VendorGetBitRateLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetBitRateOpus(p_codec_info); + } + // Add checks based on return -1; @@ -343,6 +400,11 @@ int A2DP_VendorGetTrackSampleRate(const uint8_t* p_codec_info) { return A2DP_VendorGetTrackSampleRateLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetTrackSampleRateOpus(p_codec_info); + } + // Add checks based on return -1; @@ -369,6 +431,11 @@ int A2DP_VendorGetTrackBitsPerSample(const uint8_t* p_codec_info) { return A2DP_VendorGetTrackBitsPerSampleLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetTrackBitsPerSampleOpus(p_codec_info); + } + // Add checks based on return -1; @@ -395,6 +462,11 @@ int A2DP_VendorGetTrackChannelCount(const uint8_t* p_codec_info) { return A2DP_VendorGetTrackChannelCountLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetTrackChannelCountOpus(p_codec_info); + } + // Add checks based on return -1; @@ -412,6 +484,11 @@ int A2DP_VendorGetSinkTrackChannelType(const uint8_t* p_codec_info) { return A2DP_VendorGetSinkTrackChannelTypeLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetSinkTrackChannelTypeOpus(p_codec_info); + } + return -1; } @@ -439,6 +516,11 @@ bool A2DP_VendorGetPacketTimestamp(const uint8_t* p_codec_info, return A2DP_VendorGetPacketTimestampLdac(p_codec_info, p_data, p_timestamp); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetPacketTimestampOpus(p_codec_info, p_data, p_timestamp); + } + // Add checks based on return false; @@ -469,6 +551,12 @@ bool A2DP_VendorBuildCodecHeader(const uint8_t* p_codec_info, BT_HDR* p_buf, frames_per_packet); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorBuildCodecHeaderOpus(p_codec_info, p_buf, + frames_per_packet); + } + // Add checks based on return false; @@ -496,6 +584,11 @@ const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterface( return A2DP_VendorGetEncoderInterfaceLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetEncoderInterfaceOpus(p_codec_info); + } + // Add checks based on return NULL; @@ -514,6 +607,11 @@ const tA2DP_DECODER_INTERFACE* A2DP_VendorGetDecoderInterface( return A2DP_VendorGetDecoderInterfaceLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorGetDecoderInterfaceOpus(p_codec_info); + } + return NULL; } @@ -538,6 +636,11 @@ bool A2DP_VendorAdjustCodec(uint8_t* p_codec_info) { return A2DP_VendorAdjustCodecLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorAdjustCodecOpus(p_codec_info); + } + // Add checks based on return false; @@ -565,6 +668,11 @@ btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndex( return A2DP_VendorSourceCodecIndexLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorSourceCodecIndexOpus(p_codec_info); + } + // Add checks based on return BTAV_A2DP_CODEC_INDEX_MAX; @@ -582,6 +690,11 @@ btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndex(const uint8_t* p_codec_info) { return A2DP_VendorSinkCodecIndexLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorSinkCodecIndexOpus(p_codec_info); + } + return BTAV_A2DP_CODEC_INDEX_MAX; } @@ -604,11 +717,9 @@ const char* A2DP_VendorCodecIndexStr(btav_a2dp_codec_index_t codec_index) { case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: return "LC3 not implemented"; case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: - // TODO(b/226441860): in-progress - return "Opus"; + return A2DP_VendorCodecIndexStrOpus(); case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: - // TODO(b/226441860): in-progress - return "Opus SINK"; + return A2DP_VendorCodecIndexStrOpusSink(); // Add a switch statement for each vendor-specific codec case BTAV_A2DP_CODEC_INDEX_MAX: break; @@ -637,9 +748,9 @@ bool A2DP_VendorInitCodecConfig(btav_a2dp_codec_index_t codec_index, case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: break; // not implemented case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: - // TODO(b/226441860): in-progress + return A2DP_VendorInitCodecConfigOpus(p_cfg); case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: - // TODO(b/226441860): in-progress + return A2DP_VendorInitCodecConfigOpusSink(p_cfg); // Add a switch statement for each vendor-specific codec case BTAV_A2DP_CODEC_INDEX_MAX: break; @@ -669,6 +780,11 @@ std::string A2DP_VendorCodecInfoString(const uint8_t* p_codec_info) { return A2DP_VendorCodecInfoStringLdac(p_codec_info); } + // Check for Opus + if (vendor_id == A2DP_OPUS_VENDOR_ID && codec_id == A2DP_OPUS_CODEC_ID) { + return A2DP_VendorCodecInfoStringOpus(p_codec_info); + } + // Add checks based on return "Unsupported codec vendor_id: " + loghex(vendor_id) + diff --git a/system/stack/a2dp/a2dp_vendor_opus.cc b/system/stack/a2dp/a2dp_vendor_opus.cc new file mode 100644 index 00000000000..8390035b0a4 --- /dev/null +++ b/system/stack/a2dp/a2dp_vendor_opus.cc @@ -0,0 +1,1332 @@ +/* + * Copyright 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. + */ + +/****************************************************************************** + * + * Utility functions to help build and parse the Opus Codec Information + * Element and Media Payload. + * + ******************************************************************************/ + +#define LOG_TAG "a2dp_vendor_opus" + +#include "a2dp_vendor_opus.h" + +#include +#include + +#include "a2dp_vendor.h" +#include "a2dp_vendor_opus_decoder.h" +#include "a2dp_vendor_opus_encoder.h" +#include "bt_target.h" +#include "bt_utils.h" +#include "btif_av_co.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" + +// data type for the Opus Codec Information Element */ +// NOTE: bits_per_sample and frameSize for Opus encoder initialization. +typedef struct { + uint32_t vendorId; + uint16_t codecId; /* Codec ID for Opus */ + uint8_t sampleRate; /* Sampling Frequency */ + uint8_t channelMode; /* STEREO/DUAL/MONO */ + btav_a2dp_codec_bits_per_sample_t bits_per_sample; + uint8_t future1; /* codec_specific_1 framesize */ + uint8_t future2; /* codec_specific_2 */ + uint8_t future3; /* codec_specific_3 */ + uint8_t future4; /* codec_specific_4 */ +} tA2DP_OPUS_CIE; + +/* Opus Source codec capabilities */ +static const tA2DP_OPUS_CIE a2dp_opus_source_caps = { + A2DP_OPUS_VENDOR_ID, // vendorId + A2DP_OPUS_CODEC_ID, // codecId + // sampleRate + (A2DP_OPUS_SAMPLING_FREQ_48000), + // channelMode + (A2DP_OPUS_CHANNEL_MODE_STEREO), + // bits_per_sample + (BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16), + // future 1 frameSize + (A2DP_OPUS_20MS_FRAMESIZE), + // future 2 + 0x00, + // future 3 + 0x00, + // future 4 + 0x00}; + +/* Opus Sink codec capabilities */ +static const tA2DP_OPUS_CIE a2dp_opus_sink_caps = { + A2DP_OPUS_VENDOR_ID, // vendorId + A2DP_OPUS_CODEC_ID, // codecId + // sampleRate + (A2DP_OPUS_SAMPLING_FREQ_48000), + // channelMode + (A2DP_OPUS_CHANNEL_MODE_STEREO), + // bits_per_sample + (BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16), + // future 1 frameSize + (A2DP_OPUS_20MS_FRAMESIZE), + // future 2 + 0x00, + // future 3 + 0x00, + // future 4 + 0x00}; + +/* Default Opus codec configuration */ +static const tA2DP_OPUS_CIE a2dp_opus_default_config = { + A2DP_OPUS_VENDOR_ID, // vendorId + A2DP_OPUS_CODEC_ID, // codecId + A2DP_OPUS_SAMPLING_FREQ_48000, // sampleRate + A2DP_OPUS_CHANNEL_MODE_STEREO, // channelMode + BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16, // bits_per_sample + A2DP_OPUS_20MS_FRAMESIZE, // frameSize + 0x00, // future 2 + 0x00, // future 3 + 0x00 // future 4 +}; + +static const tA2DP_ENCODER_INTERFACE a2dp_encoder_interface_opus = { + a2dp_vendor_opus_encoder_init, + a2dp_vendor_opus_encoder_cleanup, + a2dp_vendor_opus_feeding_reset, + a2dp_vendor_opus_feeding_flush, + a2dp_vendor_opus_get_encoder_interval_ms, + a2dp_vendor_opus_get_effective_frame_size, + a2dp_vendor_opus_send_frames, + a2dp_vendor_opus_set_transmit_queue_length}; + +static const tA2DP_DECODER_INTERFACE a2dp_decoder_interface_opus = { + a2dp_vendor_opus_decoder_init, a2dp_vendor_opus_decoder_cleanup, + a2dp_vendor_opus_decoder_decode_packet, a2dp_vendor_opus_decoder_start, + a2dp_vendor_opus_decoder_suspend, a2dp_vendor_opus_decoder_configure, +}; + +UNUSED_ATTR static tA2DP_STATUS A2DP_CodecInfoMatchesCapabilityOpus( + const tA2DP_OPUS_CIE* p_cap, const uint8_t* p_codec_info, + bool is_peer_codec_info); + +// Builds the Opus Media Codec Capabilities byte sequence beginning from the +// LOSC octet. |media_type| is the media type |AVDT_MEDIA_TYPE_*|. +// |p_ie| is a pointer to the Opus Codec Information Element information. +// The result is stored in |p_result|. Returns A2DP_SUCCESS on success, +// otherwise the corresponding A2DP error status code. +static tA2DP_STATUS A2DP_BuildInfoOpus(uint8_t media_type, + const tA2DP_OPUS_CIE* p_ie, + uint8_t* p_result) { + if (p_ie == NULL || p_result == NULL) { + LOG_ERROR("invalid information element"); + return A2DP_INVALID_PARAMS; + } + + *p_result++ = A2DP_OPUS_CODEC_LEN; + *p_result++ = (media_type << 4); + *p_result++ = A2DP_MEDIA_CT_NON_A2DP; + + // Vendor ID and Codec ID + *p_result++ = (uint8_t)(p_ie->vendorId & 0x000000FF); + *p_result++ = (uint8_t)((p_ie->vendorId & 0x0000FF00) >> 8); + *p_result++ = (uint8_t)((p_ie->vendorId & 0x00FF0000) >> 16); + *p_result++ = (uint8_t)((p_ie->vendorId & 0xFF000000) >> 24); + *p_result++ = (uint8_t)(p_ie->codecId & 0x00FF); + *p_result++ = (uint8_t)((p_ie->codecId & 0xFF00) >> 8); + + *p_result = 0; + *p_result |= (uint8_t)(p_ie->channelMode) & A2DP_OPUS_CHANNEL_MODE_MASK; + if ((*p_result & A2DP_OPUS_CHANNEL_MODE_MASK) == 0) { + LOG_ERROR("channelmode 0x%X setting failed", (p_ie->channelMode)); + return A2DP_INVALID_PARAMS; + } + + *p_result |= ((uint8_t)(p_ie->future1) & A2DP_OPUS_FRAMESIZE_MASK); + if ((*p_result & A2DP_OPUS_FRAMESIZE_MASK) == 0) { + LOG_ERROR("frameSize 0x%X setting failed", (p_ie->future1)); + return A2DP_INVALID_PARAMS; + } + + *p_result |= ((uint8_t)(p_ie->sampleRate) & A2DP_OPUS_SAMPLING_FREQ_MASK); + if ((*p_result & A2DP_OPUS_SAMPLING_FREQ_MASK) == 0) { + LOG_ERROR("samplerate 0x%X setting failed", (p_ie->sampleRate)); + return A2DP_INVALID_PARAMS; + } + + p_result++; + + return A2DP_SUCCESS; +} + +// Parses the Opus Media Codec Capabilities byte sequence beginning from the +// LOSC octet. The result is stored in |p_ie|. The byte sequence to parse is +// |p_codec_info|. If |is_capability| is true, the byte sequence is +// codec capabilities, otherwise is codec configuration. +// Returns A2DP_SUCCESS on success, otherwise the corresponding A2DP error +// status code. +static tA2DP_STATUS A2DP_ParseInfoOpus(tA2DP_OPUS_CIE* p_ie, + const uint8_t* p_codec_info, + bool is_capability) { + uint8_t losc; + uint8_t media_type; + tA2DP_CODEC_TYPE codec_type; + + if (p_ie == NULL || p_codec_info == NULL) { + LOG_ERROR("unable to parse information element"); + return A2DP_INVALID_PARAMS; + } + + // Check the codec capability length + losc = *p_codec_info++; + if (losc != A2DP_OPUS_CODEC_LEN) { + LOG_ERROR("invalid codec ie length %d", losc); + return A2DP_WRONG_CODEC; + } + + media_type = (*p_codec_info++) >> 4; + codec_type = *p_codec_info++; + /* Check the Media Type and Media Codec Type */ + if (media_type != AVDT_MEDIA_TYPE_AUDIO || + codec_type != A2DP_MEDIA_CT_NON_A2DP) { + LOG_ERROR("invalid codec"); + return A2DP_WRONG_CODEC; + } + + // Check the Vendor ID and Codec ID */ + p_ie->vendorId = (*p_codec_info & 0x000000FF) | + (*(p_codec_info + 1) << 8 & 0x0000FF00) | + (*(p_codec_info + 2) << 16 & 0x00FF0000) | + (*(p_codec_info + 3) << 24 & 0xFF000000); + p_codec_info += 4; + p_ie->codecId = + (*p_codec_info & 0x00FF) | (*(p_codec_info + 1) << 8 & 0xFF00); + p_codec_info += 2; + if (p_ie->vendorId != A2DP_OPUS_VENDOR_ID || + p_ie->codecId != A2DP_OPUS_CODEC_ID) { + LOG_ERROR("wrong vendor or codec id"); + return A2DP_WRONG_CODEC; + } + + p_ie->channelMode = *p_codec_info & A2DP_OPUS_CHANNEL_MODE_MASK; + p_ie->future1 = *p_codec_info & A2DP_OPUS_FRAMESIZE_MASK; + p_ie->sampleRate = *p_codec_info & A2DP_OPUS_SAMPLING_FREQ_MASK; + p_ie->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16; + + if (is_capability) { + // NOTE: The checks here are very liberal. We should be using more + // pedantic checks specific to the SRC or SNK as specified in the spec. + if (A2DP_BitsSet(p_ie->sampleRate) == A2DP_SET_ZERO_BIT) { + LOG_ERROR("invalid sample rate 0x%X", p_ie->sampleRate); + return A2DP_BAD_SAMP_FREQ; + } + if (A2DP_BitsSet(p_ie->channelMode) == A2DP_SET_ZERO_BIT) { + LOG_ERROR("invalid channel mode"); + return A2DP_BAD_CH_MODE; + } + + return A2DP_SUCCESS; + } + + if (A2DP_BitsSet(p_ie->sampleRate) != A2DP_SET_ONE_BIT) { + LOG_ERROR("invalid sampling frequency 0x%X", p_ie->sampleRate); + return A2DP_BAD_SAMP_FREQ; + } + if (A2DP_BitsSet(p_ie->channelMode) != A2DP_SET_ONE_BIT) { + LOG_ERROR("invalid channel mode."); + return A2DP_BAD_CH_MODE; + } + + return A2DP_SUCCESS; +} + +// Build the Opus Media Payload Header. +// |p_dst| points to the location where the header should be written to. +// If |frag| is true, the media payload frame is fragmented. +// |start| is true for the first packet of a fragmented frame. +// |last| is true for the last packet of a fragmented frame. +// If |frag| is false, |num| is the number of number of frames in the packet, +// otherwise is the number of remaining fragments (including this one). +static void A2DP_BuildMediaPayloadHeaderOpus(uint8_t* p_dst, bool frag, + bool start, bool last, + uint8_t num) { + if (p_dst == NULL) return; + + *p_dst = 0; + if (frag) *p_dst |= A2DP_OPUS_HDR_F_MSK; + if (start) *p_dst |= A2DP_OPUS_HDR_S_MSK; + if (last) *p_dst |= A2DP_OPUS_HDR_L_MSK; + *p_dst |= (A2DP_OPUS_HDR_NUM_MSK & num); +} + +bool A2DP_IsVendorSourceCodecValidOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE cfg_cie; + + /* Use a liberal check when parsing the codec info */ + return (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) || + (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS); +} + +bool A2DP_IsVendorSinkCodecValidOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE cfg_cie; + + /* Use a liberal check when parsing the codec info */ + return (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) || + (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS); +} + +bool A2DP_IsVendorPeerSourceCodecValidOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE cfg_cie; + + /* Use a liberal check when parsing the codec info */ + return (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) || + (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS); +} + +bool A2DP_IsVendorPeerSinkCodecValidOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE cfg_cie; + + /* Use a liberal check when parsing the codec info */ + return (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, false) == A2DP_SUCCESS) || + (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, true) == A2DP_SUCCESS); +} + +bool A2DP_IsVendorSinkCodecSupportedOpus(const uint8_t* p_codec_info) { + return A2DP_CodecInfoMatchesCapabilityOpus(&a2dp_opus_sink_caps, p_codec_info, + false) == A2DP_SUCCESS; +} +bool A2DP_IsPeerSourceCodecSupportedOpus(const uint8_t* p_codec_info) { + return A2DP_CodecInfoMatchesCapabilityOpus(&a2dp_opus_sink_caps, p_codec_info, + true) == A2DP_SUCCESS; +} + +// Checks whether A2DP Opus codec configuration matches with a device's codec +// capabilities. |p_cap| is the Opus codec configuration. |p_codec_info| is +// the device's codec capabilities. +// If |is_capability| is true, the byte sequence is codec capabilities, +// otherwise is codec configuration. +// |p_codec_info| contains the codec capabilities for a peer device that +// is acting as an A2DP source. +// Returns A2DP_SUCCESS if the codec configuration matches with capabilities, +// otherwise the corresponding A2DP error status code. +static tA2DP_STATUS A2DP_CodecInfoMatchesCapabilityOpus( + const tA2DP_OPUS_CIE* p_cap, const uint8_t* p_codec_info, + bool is_capability) { + tA2DP_STATUS status; + tA2DP_OPUS_CIE cfg_cie; + + /* parse configuration */ + status = A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, is_capability); + if (status != A2DP_SUCCESS) { + LOG_ERROR("parsing failed %d", status); + return status; + } + + /* verify that each parameter is in range */ + + LOG_VERBOSE("SAMPLING FREQ peer: 0x%x, capability 0x%x", cfg_cie.sampleRate, + p_cap->sampleRate); + LOG_VERBOSE("CH_MODE peer: 0x%x, capability 0x%x", cfg_cie.channelMode, + p_cap->channelMode); + LOG_VERBOSE("FRAMESIZE peer: 0x%x, capability 0x%x", cfg_cie.future1, + p_cap->future1); + + /* sampling frequency */ + if ((cfg_cie.sampleRate & p_cap->sampleRate) == 0) return A2DP_NS_SAMP_FREQ; + + /* channel mode */ + if ((cfg_cie.channelMode & p_cap->channelMode) == 0) return A2DP_NS_CH_MODE; + + /* frameSize */ + if ((cfg_cie.future1 & p_cap->future1) == 0) return A2DP_NS_FRAMESIZE; + + return A2DP_SUCCESS; +} + +bool A2DP_VendorUsesRtpHeaderOpus(UNUSED_ATTR bool content_protection_enabled, + UNUSED_ATTR const uint8_t* p_codec_info) { + return true; +} + +const char* A2DP_VendorCodecNameOpus(UNUSED_ATTR const uint8_t* p_codec_info) { + return "Opus"; +} + +bool A2DP_VendorCodecTypeEqualsOpus(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b) { + tA2DP_OPUS_CIE Opus_cie_a; + tA2DP_OPUS_CIE Opus_cie_b; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = + A2DP_ParseInfoOpus(&Opus_cie_a, p_codec_info_a, true); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return false; + } + a2dp_status = A2DP_ParseInfoOpus(&Opus_cie_b, p_codec_info_b, true); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return false; + } + + return true; +} + +bool A2DP_VendorCodecEqualsOpus(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b) { + tA2DP_OPUS_CIE Opus_cie_a; + tA2DP_OPUS_CIE Opus_cie_b; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = + A2DP_ParseInfoOpus(&Opus_cie_a, p_codec_info_a, true); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return false; + } + a2dp_status = A2DP_ParseInfoOpus(&Opus_cie_b, p_codec_info_b, true); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return false; + } + + return (Opus_cie_a.sampleRate == Opus_cie_b.sampleRate) && + (Opus_cie_a.channelMode == Opus_cie_b.channelMode) && + (Opus_cie_a.future1 == Opus_cie_b.future1); +} + +int A2DP_VendorGetBitRateOpus(const uint8_t* p_codec_info) { + int channel_count = A2DP_VendorGetTrackChannelCountOpus(p_codec_info); + int framesize = A2DP_VendorGetFrameSizeOpus(p_codec_info); + int samplerate = A2DP_VendorGetTrackSampleRateOpus(p_codec_info); + + // in milliseconds + switch ((framesize * 1000) / samplerate) { + case 20: + if (channel_count == 2) { + return 256000; + } else if (channel_count == 1) { + return 128000; + } else + return -1; + default: + return -1; + } +} + +int A2DP_VendorGetTrackSampleRateOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + + switch (Opus_cie.sampleRate) { + case A2DP_OPUS_SAMPLING_FREQ_48000: + return 48000; + } + + return -1; +} + +int A2DP_VendorGetTrackBitsPerSampleOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + + switch (Opus_cie.bits_per_sample) { + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16: + return 16; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24: + return 24; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32: + return 32; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE: + default: + LOG_ERROR("Invalid bit depth setting"); + return -1; + } +} + +int A2DP_VendorGetTrackChannelCountOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + + switch (Opus_cie.channelMode) { + case A2DP_OPUS_CHANNEL_MODE_MONO: + return 1; + case A2DP_OPUS_CHANNEL_MODE_STEREO: + case A2DP_OPUS_CHANNEL_MODE_DUAL_MONO: + return 2; + default: + LOG_ERROR("Invalid channel setting"); + } + + return -1; +} + +int A2DP_VendorGetSinkTrackChannelTypeOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + + switch (Opus_cie.channelMode) { + case A2DP_OPUS_CHANNEL_MODE_MONO: + return 1; + case A2DP_OPUS_CHANNEL_MODE_STEREO: + return 2; + } + + return -1; +} + +int A2DP_VendorGetChannelModeCodeOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + + switch (Opus_cie.channelMode) { + case A2DP_OPUS_CHANNEL_MODE_MONO: + case A2DP_OPUS_CHANNEL_MODE_STEREO: + return Opus_cie.channelMode; + default: + break; + } + + return -1; +} + +int A2DP_VendorGetFrameSizeOpus(const uint8_t* p_codec_info) { + tA2DP_OPUS_CIE Opus_cie; + + // Check whether the codec info contains valid data + tA2DP_STATUS a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, false); + if (a2dp_status != A2DP_SUCCESS) { + LOG_ERROR("cannot decode codec information: %d", a2dp_status); + return -1; + } + int samplerate = A2DP_VendorGetTrackSampleRateOpus(p_codec_info); + + switch (Opus_cie.future1) { + case A2DP_OPUS_20MS_FRAMESIZE: + if (samplerate == 48000) { + return 960; + } + } + + return -1; +} + +bool A2DP_VendorGetPacketTimestampOpus(UNUSED_ATTR const uint8_t* p_codec_info, + const uint8_t* p_data, + uint32_t* p_timestamp) { + *p_timestamp = *(const uint32_t*)p_data; + return true; +} + +bool A2DP_VendorBuildCodecHeaderOpus(UNUSED_ATTR const uint8_t* p_codec_info, + BT_HDR* p_buf, + uint16_t frames_per_packet) { + uint8_t* p; + + p_buf->offset -= A2DP_OPUS_MPL_HDR_LEN; + p = (uint8_t*)(p_buf + 1) + p_buf->offset; + p_buf->len += A2DP_OPUS_MPL_HDR_LEN; + + A2DP_BuildMediaPayloadHeaderOpus(p, false, false, false, + (uint8_t)frames_per_packet); + + return true; +} + +std::string A2DP_VendorCodecInfoStringOpus(const uint8_t* p_codec_info) { + std::stringstream res; + std::string field; + tA2DP_STATUS a2dp_status; + tA2DP_OPUS_CIE Opus_cie; + + a2dp_status = A2DP_ParseInfoOpus(&Opus_cie, p_codec_info, true); + if (a2dp_status != A2DP_SUCCESS) { + res << "A2DP_ParseInfoOpus fail: " << loghex(a2dp_status); + return res.str(); + } + + res << "\tname: Opus\n"; + + // Sample frequency + field.clear(); + AppendField(&field, (Opus_cie.sampleRate == 0), "NONE"); + AppendField(&field, (Opus_cie.sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000), + "48000"); + res << "\tsamp_freq: " << field << " (" << loghex(Opus_cie.sampleRate) + << ")\n"; + + // Channel mode + field.clear(); + AppendField(&field, (Opus_cie.channelMode == 0), "NONE"); + AppendField(&field, (Opus_cie.channelMode & A2DP_OPUS_CHANNEL_MODE_MONO), + "Mono"); + AppendField(&field, (Opus_cie.channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO), + "Stereo"); + res << "\tch_mode: " << field << " (" << loghex(Opus_cie.channelMode) + << ")\n"; + + // Framesize + field.clear(); + AppendField(&field, (Opus_cie.future1 == 0), "NONE"); + AppendField(&field, (Opus_cie.future1 & A2DP_OPUS_20MS_FRAMESIZE), "20ms"); + AppendField(&field, (Opus_cie.future1 & A2DP_OPUS_10MS_FRAMESIZE), "10ms"); + res << "\tframesize: " << field << " (" << loghex(Opus_cie.future1) << ")\n"; + + return res.str(); +} + +const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterfaceOpus( + const uint8_t* p_codec_info) { + if (!A2DP_IsVendorSourceCodecValidOpus(p_codec_info)) return NULL; + + return &a2dp_encoder_interface_opus; +} + +const tA2DP_DECODER_INTERFACE* A2DP_VendorGetDecoderInterfaceOpus( + const uint8_t* p_codec_info) { + if (!A2DP_IsVendorSinkCodecValidOpus(p_codec_info)) return NULL; + + return &a2dp_decoder_interface_opus; +} + +bool A2DP_VendorAdjustCodecOpus(uint8_t* p_codec_info) { + tA2DP_OPUS_CIE cfg_cie; + + // Nothing to do: just verify the codec info is valid + if (A2DP_ParseInfoOpus(&cfg_cie, p_codec_info, true) != A2DP_SUCCESS) + return false; + + return true; +} + +btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexOpus( + UNUSED_ATTR const uint8_t* p_codec_info) { + return BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS; +} + +btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndexOpus( + UNUSED_ATTR const uint8_t* p_codec_info) { + return BTAV_A2DP_CODEC_INDEX_SINK_OPUS; +} + +const char* A2DP_VendorCodecIndexStrOpus(void) { return "Opus"; } + +const char* A2DP_VendorCodecIndexStrOpusSink(void) { return "Opus SINK"; } + +bool A2DP_VendorInitCodecConfigOpus(AvdtpSepConfig* p_cfg) { + if (A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &a2dp_opus_source_caps, + p_cfg->codec_info) != A2DP_SUCCESS) { + return false; + } + +#if (BTA_AV_CO_CP_SCMS_T == TRUE) + /* Content protection info - support SCMS-T */ + uint8_t* p = p_cfg->protect_info; + *p++ = AVDT_CP_LOSC; + UINT16_TO_STREAM(p, AVDT_CP_SCMS_T_ID); + p_cfg->num_protect = 1; +#endif + + return true; +} + +bool A2DP_VendorInitCodecConfigOpusSink(AvdtpSepConfig* p_cfg) { + return A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &a2dp_opus_sink_caps, + p_cfg->codec_info) == A2DP_SUCCESS; +} + +UNUSED_ATTR static void build_codec_config(const tA2DP_OPUS_CIE& config_cie, + btav_a2dp_codec_config_t* result) { + if (config_cie.sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) + result->sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + + result->bits_per_sample = config_cie.bits_per_sample; + + if (config_cie.channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) + result->channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + if (config_cie.channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + result->channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + } + + if (config_cie.future1 & A2DP_OPUS_20MS_FRAMESIZE) + result->codec_specific_1 |= BTAV_A2DP_CODEC_FRAME_SIZE_20MS; + if (config_cie.future1 & A2DP_OPUS_10MS_FRAMESIZE) + result->codec_specific_1 |= BTAV_A2DP_CODEC_FRAME_SIZE_10MS; +} + +A2dpCodecConfigOpusSource::A2dpCodecConfigOpusSource( + btav_a2dp_codec_priority_t codec_priority) + : A2dpCodecConfigOpusBase(BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS, + A2DP_VendorCodecIndexStrOpus(), codec_priority, + true) { + // Compute the local capability + if (a2dp_opus_source_caps.sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + codec_local_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + } + codec_local_capability_.bits_per_sample = + a2dp_opus_source_caps.bits_per_sample; + if (a2dp_opus_source_caps.channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + codec_local_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + } + if (a2dp_opus_source_caps.channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + codec_local_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + } +} + +A2dpCodecConfigOpusSource::~A2dpCodecConfigOpusSource() {} + +bool A2dpCodecConfigOpusSource::init() { + if (!isValid()) return false; + + return true; +} + +bool A2dpCodecConfigOpusSource::useRtpHeaderMarkerBit() const { return false; } + +// +// Selects the best sample rate from |sampleRate|. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_best_sample_rate(uint8_t sampleRate, + tA2DP_OPUS_CIE* p_result, + btav_a2dp_codec_config_t* p_codec_config) { + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + p_result->sampleRate = A2DP_OPUS_SAMPLING_FREQ_48000; + p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + return true; + } + return false; +} + +// +// Selects the audio sample rate from |p_codec_audio_config|. +// |sampleRate| contains the capability. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_audio_sample_rate( + const btav_a2dp_codec_config_t* p_codec_audio_config, uint8_t sampleRate, + tA2DP_OPUS_CIE* p_result, btav_a2dp_codec_config_t* p_codec_config) { + switch (p_codec_audio_config->sample_rate) { + case BTAV_A2DP_CODEC_SAMPLE_RATE_48000: + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + p_result->sampleRate = A2DP_OPUS_SAMPLING_FREQ_48000; + p_codec_config->sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + return true; + } + break; + case BTAV_A2DP_CODEC_SAMPLE_RATE_16000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_24000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_44100: + case BTAV_A2DP_CODEC_SAMPLE_RATE_88200: + case BTAV_A2DP_CODEC_SAMPLE_RATE_96000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_176400: + case BTAV_A2DP_CODEC_SAMPLE_RATE_192000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE: + break; + } + + return false; +} + +// +// Selects the best bits per sample from |bits_per_sample|. +// |bits_per_sample| contains the capability. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_best_bits_per_sample( + btav_a2dp_codec_bits_per_sample_t bits_per_sample, tA2DP_OPUS_CIE* p_result, + btav_a2dp_codec_config_t* p_codec_config) { + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32; + return true; + } + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24; + return true; + } + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16; + return true; + } + return false; +} + +// +// Selects the audio bits per sample from |p_codec_audio_config|. +// |bits_per_sample| contains the capability. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_audio_bits_per_sample( + const btav_a2dp_codec_config_t* p_codec_audio_config, + btav_a2dp_codec_bits_per_sample_t bits_per_sample, tA2DP_OPUS_CIE* p_result, + btav_a2dp_codec_config_t* p_codec_config) { + switch (p_codec_audio_config->bits_per_sample) { + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16; + return true; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24; + return true; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) { + p_codec_config->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32; + p_result->bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32; + return true; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE: + break; + } + return false; +} + +// +// Selects the best channel mode from |channelMode|. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_best_channel_mode(uint8_t channelMode, + tA2DP_OPUS_CIE* p_result, + btav_a2dp_codec_config_t* p_codec_config) { + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + p_result->channelMode = A2DP_OPUS_CHANNEL_MODE_STEREO; + p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + return true; + } + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + p_result->channelMode = A2DP_OPUS_CHANNEL_MODE_MONO; + p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + return true; + } + return false; +} + +// +// Selects the audio channel mode from |p_codec_audio_config|. +// |channelMode| contains the capability. +// The result is stored in |p_result| and |p_codec_config|. +// Returns true if a selection was made, otherwise false. +// +static bool select_audio_channel_mode( + const btav_a2dp_codec_config_t* p_codec_audio_config, uint8_t channelMode, + tA2DP_OPUS_CIE* p_result, btav_a2dp_codec_config_t* p_codec_config) { + switch (p_codec_audio_config->channel_mode) { + case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO: + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + p_result->channelMode = A2DP_OPUS_CHANNEL_MODE_MONO; + p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + return true; + } + break; + case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO: + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + p_result->channelMode = A2DP_OPUS_CHANNEL_MODE_STEREO; + p_codec_config->channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + return true; + } + break; + case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE: + break; + } + + return false; +} + +bool A2dpCodecConfigOpusBase::setCodecConfig(const uint8_t* p_peer_codec_info, + bool is_capability, + uint8_t* p_result_codec_config) { + std::lock_guard lock(codec_mutex_); + tA2DP_OPUS_CIE peer_info_cie; + tA2DP_OPUS_CIE result_config_cie; + uint8_t channelMode; + uint8_t sampleRate; + uint8_t frameSize; + btav_a2dp_codec_bits_per_sample_t bits_per_sample; + const tA2DP_OPUS_CIE* p_a2dp_opus_caps = + (is_source_) ? &a2dp_opus_source_caps : &a2dp_opus_sink_caps; + + btav_a2dp_codec_config_t device_codec_config_ = getCodecConfig(); + + LOG_INFO( + "AudioManager stream config %d sample rate %d bit depth %d channel " + "mode", + device_codec_config_.sample_rate, device_codec_config_.bits_per_sample, + device_codec_config_.channel_mode); + + // Save the internal state + btav_a2dp_codec_config_t saved_codec_config = codec_config_; + btav_a2dp_codec_config_t saved_codec_capability = codec_capability_; + btav_a2dp_codec_config_t saved_codec_selectable_capability = + codec_selectable_capability_; + btav_a2dp_codec_config_t saved_codec_user_config = codec_user_config_; + btav_a2dp_codec_config_t saved_codec_audio_config = codec_audio_config_; + uint8_t saved_ota_codec_config[AVDT_CODEC_SIZE]; + uint8_t saved_ota_codec_peer_capability[AVDT_CODEC_SIZE]; + uint8_t saved_ota_codec_peer_config[AVDT_CODEC_SIZE]; + memcpy(saved_ota_codec_config, ota_codec_config_, sizeof(ota_codec_config_)); + memcpy(saved_ota_codec_peer_capability, ota_codec_peer_capability_, + sizeof(ota_codec_peer_capability_)); + memcpy(saved_ota_codec_peer_config, ota_codec_peer_config_, + sizeof(ota_codec_peer_config_)); + + tA2DP_STATUS status = + A2DP_ParseInfoOpus(&peer_info_cie, p_peer_codec_info, is_capability); + if (status != A2DP_SUCCESS) { + LOG_ERROR("can't parse peer's capabilities: error = %d", status); + goto fail; + } + + // + // Build the preferred configuration + // + memset(&result_config_cie, 0, sizeof(result_config_cie)); + result_config_cie.vendorId = p_a2dp_opus_caps->vendorId; + result_config_cie.codecId = p_a2dp_opus_caps->codecId; + + // + // Select the sample frequency + // + sampleRate = p_a2dp_opus_caps->sampleRate & peer_info_cie.sampleRate; + codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE; + + switch (codec_user_config_.sample_rate) { + case BTAV_A2DP_CODEC_SAMPLE_RATE_48000: + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + result_config_cie.sampleRate = A2DP_OPUS_SAMPLING_FREQ_48000; + codec_capability_.sample_rate = codec_user_config_.sample_rate; + codec_config_.sample_rate = codec_user_config_.sample_rate; + } + break; + case BTAV_A2DP_CODEC_SAMPLE_RATE_44100: + case BTAV_A2DP_CODEC_SAMPLE_RATE_88200: + case BTAV_A2DP_CODEC_SAMPLE_RATE_96000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_176400: + case BTAV_A2DP_CODEC_SAMPLE_RATE_192000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_16000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_24000: + case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE: + codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE; + codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE; + break; + } + + // Select the sample frequency if there is no user preference + do { + // Compute the selectable capability + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + codec_selectable_capability_.sample_rate |= + BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + } + + if (codec_config_.sample_rate != BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) break; + + // Compute the common capability + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) + codec_capability_.sample_rate |= BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + + // No user preference - try the codec audio config + if (select_audio_sample_rate(&codec_audio_config_, sampleRate, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - try the default config + if (select_best_sample_rate( + a2dp_opus_default_config.sampleRate & peer_info_cie.sampleRate, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - use the best match + if (select_best_sample_rate(sampleRate, &result_config_cie, + &codec_config_)) { + break; + } + } while (false); + if (codec_config_.sample_rate == BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) { + LOG_ERROR( + "cannot match sample frequency: local caps = 0x%x " + "peer info = 0x%x", + p_a2dp_opus_caps->sampleRate, peer_info_cie.sampleRate); + goto fail; + } + + // + // Select the bits per sample + // + // NOTE: this information is NOT included in the Opus A2DP codec description + // that is sent OTA. + bits_per_sample = p_a2dp_opus_caps->bits_per_sample; + codec_config_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE; + switch (codec_user_config_.bits_per_sample) { + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) { + result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample; + codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample; + codec_config_.bits_per_sample = codec_user_config_.bits_per_sample; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) { + result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample; + codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample; + codec_config_.bits_per_sample = codec_user_config_.bits_per_sample; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32: + if (bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) { + result_config_cie.bits_per_sample = codec_user_config_.bits_per_sample; + codec_capability_.bits_per_sample = codec_user_config_.bits_per_sample; + codec_config_.bits_per_sample = codec_user_config_.bits_per_sample; + } + break; + case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE: + result_config_cie.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE; + codec_capability_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE; + codec_config_.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE; + break; + } + + // Select the bits per sample if there is no user preference + do { + // Compute the selectable capability + codec_selectable_capability_.bits_per_sample = + p_a2dp_opus_caps->bits_per_sample; + + if (codec_config_.bits_per_sample != BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE) + break; + + // Compute the common capability + codec_capability_.bits_per_sample = bits_per_sample; + + // No user preference - try yhe codec audio config + if (select_audio_bits_per_sample(&codec_audio_config_, + p_a2dp_opus_caps->bits_per_sample, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - try the default config + if (select_best_bits_per_sample(a2dp_opus_default_config.bits_per_sample, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - use the best match + if (select_best_bits_per_sample(p_a2dp_opus_caps->bits_per_sample, + &result_config_cie, &codec_config_)) { + break; + } + } while (false); + if (codec_config_.bits_per_sample == BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE) { + LOG_ERROR( + "cannot match bits per sample: default = 0x%x " + "user preference = 0x%x", + a2dp_opus_default_config.bits_per_sample, + codec_user_config_.bits_per_sample); + goto fail; + } + + // + // Select the channel mode + // + channelMode = p_a2dp_opus_caps->channelMode & peer_info_cie.channelMode; + codec_config_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE; + switch (codec_user_config_.channel_mode) { + case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO: + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + result_config_cie.channelMode = A2DP_OPUS_CHANNEL_MODE_MONO; + codec_capability_.channel_mode = codec_user_config_.channel_mode; + codec_config_.channel_mode = codec_user_config_.channel_mode; + } + break; + case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO: + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + result_config_cie.channelMode = A2DP_OPUS_CHANNEL_MODE_STEREO; + codec_capability_.channel_mode = codec_user_config_.channel_mode; + codec_config_.channel_mode = codec_user_config_.channel_mode; + } + break; + case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE: + codec_capability_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE; + codec_config_.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE; + break; + } + + // Select the channel mode if there is no user preference + do { + // Compute the selectable capability + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + codec_selectable_capability_.channel_mode |= + BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + } + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + codec_selectable_capability_.channel_mode |= + BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + } + + if (codec_config_.channel_mode != BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) break; + + // Compute the common capability + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) + codec_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + codec_capability_.channel_mode |= BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + } + + // No user preference - try the codec audio config + if (select_audio_channel_mode(&codec_audio_config_, channelMode, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - try the default config + if (select_best_channel_mode( + a2dp_opus_default_config.channelMode & peer_info_cie.channelMode, + &result_config_cie, &codec_config_)) { + break; + } + + // No user preference - use the best match + if (select_best_channel_mode(channelMode, &result_config_cie, + &codec_config_)) { + break; + } + } while (false); + if (codec_config_.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) { + LOG_ERROR( + "cannot match channel mode: local caps = 0x%x " + "peer info = 0x%x", + p_a2dp_opus_caps->channelMode, peer_info_cie.channelMode); + goto fail; + } + + // + // Select the frame size + // + frameSize = p_a2dp_opus_caps->future1 & peer_info_cie.future1; + codec_config_.codec_specific_1 = BTAV_A2DP_CODEC_FRAME_SIZE_NONE; + switch (codec_user_config_.codec_specific_1) { + case BTAV_A2DP_CODEC_FRAME_SIZE_20MS: + if (frameSize & A2DP_OPUS_20MS_FRAMESIZE) { + result_config_cie.future1 = A2DP_OPUS_20MS_FRAMESIZE; + codec_capability_.codec_specific_1 = + codec_user_config_.codec_specific_1; + codec_config_.codec_specific_1 = codec_user_config_.codec_specific_1; + } + break; + case BTAV_A2DP_CODEC_FRAME_SIZE_10MS: + if (frameSize & A2DP_OPUS_10MS_FRAMESIZE) { + result_config_cie.future1 = A2DP_OPUS_10MS_FRAMESIZE; + codec_capability_.codec_specific_1 = + codec_user_config_.codec_specific_1; + codec_config_.codec_specific_1 = codec_user_config_.codec_specific_1; + } + break; + case BTAV_A2DP_CODEC_FRAME_SIZE_NONE: + codec_capability_.codec_specific_1 = BTAV_A2DP_CODEC_FRAME_SIZE_NONE; + codec_config_.codec_specific_1 = BTAV_A2DP_CODEC_FRAME_SIZE_NONE; + break; + } + + // No user preference - set default value + codec_config_.codec_specific_1 = BTAV_A2DP_CODEC_FRAME_SIZE_20MS; + result_config_cie.future1 = A2DP_OPUS_20MS_FRAMESIZE; + result_config_cie.future3 = 0x00; + + if (codec_config_.codec_specific_1 == BTAV_A2DP_CODEC_FRAME_SIZE_NONE) { + LOG_ERROR( + "cannot match frame size: local caps = 0x%x " + "peer info = 0x%x", + p_a2dp_opus_caps->future1, peer_info_cie.future1); + goto fail; + } + + if (A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &result_config_cie, + p_result_codec_config) != A2DP_SUCCESS) { + LOG_ERROR("failed to BuildInfoOpus for result_config_cie"); + goto fail; + } + + // + // Copy the codec-specific fields if they are not zero + // + if (codec_user_config_.codec_specific_1 != 0) + codec_config_.codec_specific_1 = codec_user_config_.codec_specific_1; + if (codec_user_config_.codec_specific_2 != 0) + codec_config_.codec_specific_2 = codec_user_config_.codec_specific_2; + if (codec_user_config_.codec_specific_3 != 0) + codec_config_.codec_specific_3 = codec_user_config_.codec_specific_3; + if (codec_user_config_.codec_specific_4 != 0) + codec_config_.codec_specific_4 = codec_user_config_.codec_specific_4; + + // Create a local copy of the peer codec capability, and the + // result codec config. + if (is_capability) { + status = A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &peer_info_cie, + ota_codec_peer_capability_); + } else { + status = A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &peer_info_cie, + ota_codec_peer_config_); + } + CHECK(status == A2DP_SUCCESS); + + status = A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &result_config_cie, + ota_codec_config_); + CHECK(status == A2DP_SUCCESS); + return true; + +fail: + // Restore the internal state + codec_config_ = saved_codec_config; + codec_capability_ = saved_codec_capability; + codec_selectable_capability_ = saved_codec_selectable_capability; + codec_user_config_ = saved_codec_user_config; + codec_audio_config_ = saved_codec_audio_config; + memcpy(ota_codec_config_, saved_ota_codec_config, sizeof(ota_codec_config_)); + memcpy(ota_codec_peer_capability_, saved_ota_codec_peer_capability, + sizeof(ota_codec_peer_capability_)); + memcpy(ota_codec_peer_config_, saved_ota_codec_peer_config, + sizeof(ota_codec_peer_config_)); + return false; +} + +bool A2dpCodecConfigOpusBase::setPeerCodecCapabilities( + const uint8_t* p_peer_codec_capabilities) { + std::lock_guard lock(codec_mutex_); + tA2DP_OPUS_CIE peer_info_cie; + uint8_t channelMode; + uint8_t sampleRate; + const tA2DP_OPUS_CIE* p_a2dp_opus_caps = + (is_source_) ? &a2dp_opus_source_caps : &a2dp_opus_sink_caps; + + // Save the internal state + btav_a2dp_codec_config_t saved_codec_selectable_capability = + codec_selectable_capability_; + uint8_t saved_ota_codec_peer_capability[AVDT_CODEC_SIZE]; + memcpy(saved_ota_codec_peer_capability, ota_codec_peer_capability_, + sizeof(ota_codec_peer_capability_)); + + tA2DP_STATUS status = + A2DP_ParseInfoOpus(&peer_info_cie, p_peer_codec_capabilities, true); + if (status != A2DP_SUCCESS) { + LOG_ERROR("can't parse peer's capabilities: error = %d", status); + goto fail; + } + + // Compute the selectable capability - sample rate + sampleRate = p_a2dp_opus_caps->sampleRate & peer_info_cie.sampleRate; + if (sampleRate & A2DP_OPUS_SAMPLING_FREQ_48000) { + codec_selectable_capability_.sample_rate |= + BTAV_A2DP_CODEC_SAMPLE_RATE_48000; + } + + // Compute the selectable capability - bits per sample + codec_selectable_capability_.bits_per_sample = + p_a2dp_opus_caps->bits_per_sample; + + // Compute the selectable capability - channel mode + channelMode = p_a2dp_opus_caps->channelMode & peer_info_cie.channelMode; + if (channelMode & A2DP_OPUS_CHANNEL_MODE_MONO) { + codec_selectable_capability_.channel_mode |= + BTAV_A2DP_CODEC_CHANNEL_MODE_MONO; + } + if (channelMode & A2DP_OPUS_CHANNEL_MODE_STEREO) { + codec_selectable_capability_.channel_mode |= + BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO; + } + + LOG_INFO("BuildInfoOpus for peer info cie for ota caps"); + status = A2DP_BuildInfoOpus(AVDT_MEDIA_TYPE_AUDIO, &peer_info_cie, + ota_codec_peer_capability_); + CHECK(status == A2DP_SUCCESS); + return true; + +fail: + // Restore the internal state + codec_selectable_capability_ = saved_codec_selectable_capability; + memcpy(ota_codec_peer_capability_, saved_ota_codec_peer_capability, + sizeof(ota_codec_peer_capability_)); + return false; +} + +A2dpCodecConfigOpusSink::A2dpCodecConfigOpusSink( + btav_a2dp_codec_priority_t codec_priority) + : A2dpCodecConfigOpusBase(BTAV_A2DP_CODEC_INDEX_SINK_OPUS, + A2DP_VendorCodecIndexStrOpusSink(), + codec_priority, false) {} + +A2dpCodecConfigOpusSink::~A2dpCodecConfigOpusSink() {} + +bool A2dpCodecConfigOpusSink::init() { + if (!isValid()) return false; + + return true; +} + +bool A2dpCodecConfigOpusSink::useRtpHeaderMarkerBit() const { return false; } + +bool A2dpCodecConfigOpusSink::updateEncoderUserConfig( + UNUSED_ATTR const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, + UNUSED_ATTR bool* p_restart_input, UNUSED_ATTR bool* p_restart_output, + UNUSED_ATTR bool* p_config_updated) { + return false; +} diff --git a/system/stack/a2dp/a2dp_vendor_opus_decoder.cc b/system/stack/a2dp/a2dp_vendor_opus_decoder.cc new file mode 100644 index 00000000000..20867b5a808 --- /dev/null +++ b/system/stack/a2dp/a2dp_vendor_opus_decoder.cc @@ -0,0 +1,159 @@ +/* + * 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. + */ + +#define LOG_TAG "a2dp_opus_decoder" + +#include "a2dp_vendor_opus_decoder.h" + +#include +#include + +#include "a2dp_vendor_opus.h" +#include "osi/include/allocator.h" +#include "osi/include/log.h" + +typedef struct { + OpusDecoder* opus_handle = nullptr; + bool has_opus_handle; + int16_t* decode_buf = nullptr; + decoded_data_callback_t decode_callback; +} tA2DP_OPUS_DECODER_CB; + +static tA2DP_OPUS_DECODER_CB a2dp_opus_decoder_cb; + +void a2dp_vendor_opus_decoder_cleanup(void) { + if (a2dp_opus_decoder_cb.has_opus_handle) { + osi_free(a2dp_opus_decoder_cb.opus_handle); + + if (a2dp_opus_decoder_cb.decode_buf != nullptr) { + memset(a2dp_opus_decoder_cb.decode_buf, 0, + A2DP_OPUS_DECODE_BUFFER_LENGTH); + osi_free(a2dp_opus_decoder_cb.decode_buf); + a2dp_opus_decoder_cb.decode_buf = nullptr; + } + a2dp_opus_decoder_cb.has_opus_handle = false; + } + + return; +} + +bool a2dp_vendor_opus_decoder_init(decoded_data_callback_t decode_callback) { + a2dp_vendor_opus_decoder_cleanup(); + + int32_t err_val = OPUS_OK; + int32_t size = 0; + + size = opus_decoder_get_size(A2DP_OPUS_CODEC_OUTPUT_CHS); + a2dp_opus_decoder_cb.opus_handle = + static_cast(osi_malloc(size)); + if (a2dp_opus_decoder_cb.opus_handle == nullptr) { + LOG_ERROR("failed to allocate opus decoder handle"); + return false; + } + err_val = opus_decoder_init(a2dp_opus_decoder_cb.opus_handle, + A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE, + A2DP_OPUS_CODEC_OUTPUT_CHS); + if (err_val == OPUS_OK) { + a2dp_opus_decoder_cb.has_opus_handle = true; + + a2dp_opus_decoder_cb.decode_buf = + static_cast(osi_malloc(A2DP_OPUS_DECODE_BUFFER_LENGTH)); + + memset(a2dp_opus_decoder_cb.decode_buf, 0, A2DP_OPUS_DECODE_BUFFER_LENGTH); + + a2dp_opus_decoder_cb.decode_callback = decode_callback; + LOG_INFO("decoder init success"); + return true; + } else { + LOG_ERROR("failed to initialize Opus Decoder"); + a2dp_opus_decoder_cb.has_opus_handle = false; + return false; + } + + return false; +} + +void a2dp_vendor_opus_decoder_configure(const uint8_t* p_codec_info) { return; } + +bool a2dp_vendor_opus_decoder_decode_packet(BT_HDR* p_buf) { + uint32_t frameSize; + uint32_t numChannels; + uint32_t numFrames; + int32_t ret_val = 0; + uint32_t frameLen = 0; + + if (p_buf == nullptr) { + LOG_ERROR("Dropping packet with nullptr"); + return false; + } + + auto* pBuffer = + reinterpret_cast(p_buf->data + p_buf->offset + 1); + int32_t bufferSize = p_buf->len - 1; + + numChannels = opus_packet_get_nb_channels(pBuffer); + numFrames = opus_packet_get_nb_frames(pBuffer, bufferSize); + frameSize = opus_packet_get_samples_per_frame( + pBuffer, A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE); + frameLen = opus_packet_get_nb_samples(pBuffer, bufferSize, + A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE); + uint32_t num_frames = pBuffer[0] & 0xf; + + LOG_ERROR("numframes %d framesize %d framelen %d bufferSize %d", num_frames, + frameSize, frameLen, bufferSize); + LOG_ERROR("numChannels %d numFrames %d offset %d", numChannels, numFrames, + p_buf->offset); + + for (uint32_t frame = 0; frame < numFrames; ++frame) { + { + numChannels = opus_packet_get_nb_channels(pBuffer); + + ret_val = opus_decode(a2dp_opus_decoder_cb.opus_handle, + reinterpret_cast(pBuffer), + bufferSize, a2dp_opus_decoder_cb.decode_buf, + A2DP_OPUS_DECODE_BUFFER_LENGTH, 0 /* flags */); + + if (ret_val < OPUS_OK) { + LOG_ERROR("Opus DecodeFrame failed %d, applying concealment", ret_val); + ret_val = opus_decode(a2dp_opus_decoder_cb.opus_handle, NULL, 0, + a2dp_opus_decoder_cb.decode_buf, + A2DP_OPUS_DECODE_BUFFER_LENGTH, 0 /* flags */); + } + + size_t frame_len = + ret_val * numChannels * sizeof(a2dp_opus_decoder_cb.decode_buf[0]); + a2dp_opus_decoder_cb.decode_callback( + reinterpret_cast(a2dp_opus_decoder_cb.decode_buf), + frame_len); + } + } + return true; +} + +void a2dp_vendor_opus_decoder_start(void) { return; } + +void a2dp_vendor_opus_decoder_suspend(void) { + int32_t err_val = 0; + + if (a2dp_opus_decoder_cb.has_opus_handle) { + err_val = + opus_decoder_ctl(a2dp_opus_decoder_cb.opus_handle, OPUS_RESET_STATE); + if (err_val != OPUS_OK) { + LOG_ERROR("failed to reset decoder"); + } + } + return; +} diff --git a/system/stack/a2dp/a2dp_vendor_opus_encoder.cc b/system/stack/a2dp/a2dp_vendor_opus_encoder.cc new file mode 100644 index 00000000000..ded13e4ae9c --- /dev/null +++ b/system/stack/a2dp/a2dp_vendor_opus_encoder.cc @@ -0,0 +1,532 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "a2dp_vendor_opus_encoder" +#define ATRACE_TAG ATRACE_TAG_AUDIO + +#include "a2dp_vendor_opus_encoder.h" + +#ifndef OS_GENERIC +#include +#endif +#include +#include +#include +#include +#include + +#include "a2dp_vendor.h" +#include "a2dp_vendor_opus.h" +#include "common/time_util.h" +#include "osi/include/allocator.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "stack/include/bt_hdr.h" + +typedef struct { + uint32_t sample_rate; + uint16_t bitrate; + uint16_t framesize; + uint8_t channel_mode; + uint8_t bits_per_sample; + uint8_t quality_mode_index; + int pcm_wlength; + uint8_t pcm_fmt; +} tA2DP_OPUS_ENCODER_PARAMS; + +typedef struct { + float counter; + uint32_t bytes_per_tick; + uint64_t last_frame_us; +} tA2DP_OPUS_FEEDING_STATE; + +typedef struct { + uint64_t session_start_us; + + size_t media_read_total_expected_packets; + size_t media_read_total_expected_reads_count; + size_t media_read_total_expected_read_bytes; + + size_t media_read_total_dropped_packets; + size_t media_read_total_actual_reads_count; + size_t media_read_total_actual_read_bytes; +} a2dp_opus_encoder_stats_t; + +typedef struct { + a2dp_source_read_callback_t read_callback; + a2dp_source_enqueue_callback_t enqueue_callback; + uint16_t TxAaMtuSize; + size_t TxQueueLength; + + bool use_SCMS_T; + bool is_peer_edr; // True if the peer device supports EDR + bool peer_supports_3mbps; // True if the peer device supports 3Mbps EDR + uint16_t peer_mtu; // MTU of the A2DP peer + uint32_t timestamp; // Timestamp for the A2DP frames + + OpusEncoder* opus_handle; + bool has_opus_handle; // True if opus_handle is valid + + tA2DP_FEEDING_PARAMS feeding_params; + tA2DP_OPUS_ENCODER_PARAMS opus_encoder_params; + tA2DP_OPUS_FEEDING_STATE opus_feeding_state; + + a2dp_opus_encoder_stats_t stats; +} tA2DP_OPUS_ENCODER_CB; + +static tA2DP_OPUS_ENCODER_CB a2dp_opus_encoder_cb; + +static bool a2dp_vendor_opus_encoder_update(uint16_t peer_mtu, + A2dpCodecConfig* a2dp_codec_config, + bool* p_restart_input, + bool* p_restart_output, + bool* p_config_updated); +static void a2dp_opus_get_num_frame_iteration(uint8_t* num_of_iterations, + uint8_t* num_of_frames, + uint64_t timestamp_us); +static void a2dp_opus_encode_frames(uint8_t nb_frame); +static bool a2dp_opus_read_feeding(uint8_t* read_buffer, uint32_t* bytes_read); + +void a2dp_vendor_opus_encoder_cleanup(void) { + if (a2dp_opus_encoder_cb.has_opus_handle) { + osi_free(a2dp_opus_encoder_cb.opus_handle); + a2dp_opus_encoder_cb.has_opus_handle = false; + a2dp_opus_encoder_cb.opus_handle = nullptr; + } + memset(&a2dp_opus_encoder_cb, 0, sizeof(a2dp_opus_encoder_cb)); + + a2dp_opus_encoder_cb.stats.session_start_us = + bluetooth::common::time_get_os_boottime_us(); + + a2dp_opus_encoder_cb.timestamp = 0; + +#if (BTA_AV_CO_CP_SCMS_T == TRUE) + a2dp_opus_encoder_cb.use_SCMS_T = true; +#else + a2dp_opus_encoder_cb.use_SCMS_T = false; +#endif + return; +} + +void a2dp_vendor_opus_encoder_init( + const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, + A2dpCodecConfig* a2dp_codec_config, + a2dp_source_read_callback_t read_callback, + a2dp_source_enqueue_callback_t enqueue_callback) { + uint32_t error_val; + + a2dp_vendor_opus_encoder_cleanup(); + + a2dp_opus_encoder_cb.read_callback = read_callback; + a2dp_opus_encoder_cb.enqueue_callback = enqueue_callback; + a2dp_opus_encoder_cb.is_peer_edr = p_peer_params->is_peer_edr; + a2dp_opus_encoder_cb.peer_supports_3mbps = p_peer_params->peer_supports_3mbps; + a2dp_opus_encoder_cb.peer_mtu = p_peer_params->peer_mtu; + + // NOTE: Ignore the restart_input / restart_output flags - this initization + // happens when the connection is (re)started. + bool restart_input = false; + bool restart_output = false; + bool config_updated = false; + + uint32_t size = opus_encoder_get_size(A2DP_OPUS_CODEC_OUTPUT_CHS); + a2dp_opus_encoder_cb.opus_handle = + static_cast(osi_malloc(size)); + if (a2dp_opus_encoder_cb.opus_handle == nullptr) { + LOG_ERROR("failed to allocate opus encoder handle"); + return; + } + + error_val = opus_encoder_init( + a2dp_opus_encoder_cb.opus_handle, A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE, + A2DP_OPUS_CODEC_OUTPUT_CHS, OPUS_APPLICATION_AUDIO); + + if (error_val != OPUS_OK) { + LOG_ERROR( + "failed to init opus encoder (handle size %d, sampling rate %d, " + "output chs %d, error %d)", + size, A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE, A2DP_OPUS_CODEC_OUTPUT_CHS, + error_val); + osi_free(a2dp_opus_encoder_cb.opus_handle); + return; + } else { + a2dp_opus_encoder_cb.has_opus_handle = true; + } + + a2dp_vendor_opus_encoder_update(a2dp_opus_encoder_cb.peer_mtu, + a2dp_codec_config, &restart_input, + &restart_output, &config_updated); + + return; +} + +bool A2dpCodecConfigOpusSource::updateEncoderUserConfig( + const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, bool* p_restart_input, + bool* p_restart_output, bool* p_config_updated) { + if (a2dp_opus_encoder_cb.peer_mtu == 0) { + LOG_ERROR( + "Cannot update the codec encoder for %s: " + "invalid peer MTU", + name().c_str()); + return false; + } + + return a2dp_vendor_opus_encoder_update(a2dp_opus_encoder_cb.peer_mtu, this, + p_restart_input, p_restart_output, + p_config_updated); +} + +static bool a2dp_vendor_opus_encoder_update(uint16_t peer_mtu, + A2dpCodecConfig* a2dp_codec_config, + bool* p_restart_input, + bool* p_restart_output, + bool* p_config_updated) { + tA2DP_OPUS_ENCODER_PARAMS* p_encoder_params = + &a2dp_opus_encoder_cb.opus_encoder_params; + uint8_t codec_info[AVDT_CODEC_SIZE]; + uint32_t error = 0; + + *p_restart_input = false; + *p_restart_output = false; + *p_config_updated = false; + + if (!a2dp_opus_encoder_cb.has_opus_handle || + a2dp_opus_encoder_cb.opus_handle == NULL) { + LOG_ERROR("Cannot get Opus encoder handle"); + return false; + } + CHECK(a2dp_opus_encoder_cb.opus_handle != nullptr); + + if (!a2dp_codec_config->copyOutOtaCodecConfig(codec_info)) { + LOG_ERROR( + "Cannot update the codec encoder for %s: " + "invalid codec config", + a2dp_codec_config->name().c_str()); + return false; + } + const uint8_t* p_codec_info = codec_info; + btav_a2dp_codec_config_t codec_config = a2dp_codec_config->getCodecConfig(); + + // The feeding parameters + tA2DP_FEEDING_PARAMS* p_feeding_params = &a2dp_opus_encoder_cb.feeding_params; + p_feeding_params->sample_rate = + A2DP_VendorGetTrackSampleRateOpus(p_codec_info); + p_feeding_params->bits_per_sample = + a2dp_codec_config->getAudioBitsPerSample(); + p_feeding_params->channel_count = + A2DP_VendorGetTrackChannelCountOpus(p_codec_info); + LOG_INFO("sample_rate=%u bits_per_sample=%u channel_count=%u", + p_feeding_params->sample_rate, p_feeding_params->bits_per_sample, + p_feeding_params->channel_count); + a2dp_vendor_opus_feeding_reset(); + + // The codec parameters + p_encoder_params->sample_rate = + a2dp_opus_encoder_cb.feeding_params.sample_rate; + p_encoder_params->channel_mode = + A2DP_VendorGetChannelModeCodeOpus(p_codec_info); + p_encoder_params->framesize = A2DP_VendorGetFrameSizeOpus(p_codec_info); + p_encoder_params->bitrate = A2DP_VendorGetBitRateOpus(p_codec_info); + + uint16_t mtu_size = + BT_DEFAULT_BUFFER_SIZE - A2DP_OPUS_OFFSET - sizeof(BT_HDR); + if (mtu_size < peer_mtu) { + a2dp_opus_encoder_cb.TxAaMtuSize = mtu_size; + } else { + a2dp_opus_encoder_cb.TxAaMtuSize = peer_mtu; + } + + // Set the bitrate quality mode index + if (codec_config.codec_specific_3 != 0) { + p_encoder_params->quality_mode_index = codec_config.codec_specific_3 % 10; + LOG_INFO("setting bitrate quality mode to %d", + p_encoder_params->quality_mode_index); + } else { + p_encoder_params->quality_mode_index = 5; + LOG_INFO("setting bitrate quality mode to default %d", + p_encoder_params->quality_mode_index); + } + + error = opus_encoder_ctl( + a2dp_opus_encoder_cb.opus_handle, + OPUS_SET_COMPLEXITY(p_encoder_params->quality_mode_index)); + + if (error != OPUS_OK) { + LOG_ERROR("failed to set encoder bitrate quality setting"); + return false; + } + + p_encoder_params->pcm_wlength = + a2dp_opus_encoder_cb.feeding_params.bits_per_sample >> 3; + + LOG_INFO("setting bitrate to %d", p_encoder_params->bitrate); + error = opus_encoder_ctl(a2dp_opus_encoder_cb.opus_handle, + OPUS_SET_BITRATE(p_encoder_params->bitrate)); + + if (error != OPUS_OK) { + LOG_ERROR("failed to set encoder bitrate"); + return false; + } + + // Set the Audio format from pcm_wlength + if (p_encoder_params->pcm_wlength == 2) + p_encoder_params->pcm_fmt = 16; + else if (p_encoder_params->pcm_wlength == 3) + p_encoder_params->pcm_fmt = 24; + else if (p_encoder_params->pcm_wlength == 4) + p_encoder_params->pcm_fmt = 32; + + return true; +} + +void a2dp_vendor_opus_feeding_reset(void) { + memset(&a2dp_opus_encoder_cb.opus_feeding_state, 0, + sizeof(a2dp_opus_encoder_cb.opus_feeding_state)); + + a2dp_opus_encoder_cb.opus_feeding_state.bytes_per_tick = + (a2dp_opus_encoder_cb.feeding_params.sample_rate * + a2dp_opus_encoder_cb.feeding_params.bits_per_sample / 8 * + a2dp_opus_encoder_cb.feeding_params.channel_count * + a2dp_vendor_opus_get_encoder_interval_ms()) / + 1000; + + return; +} + +void a2dp_vendor_opus_feeding_flush(void) { + a2dp_opus_encoder_cb.opus_feeding_state.counter = 0.0f; + + return; +} + +uint64_t a2dp_vendor_opus_get_encoder_interval_ms(void) { + return ((a2dp_opus_encoder_cb.opus_encoder_params.framesize * 1000) / + a2dp_opus_encoder_cb.opus_encoder_params.sample_rate); +} + +void a2dp_vendor_opus_send_frames(uint64_t timestamp_us) { + uint8_t nb_frame = 0; + uint8_t nb_iterations = 0; + + a2dp_opus_get_num_frame_iteration(&nb_iterations, &nb_frame, timestamp_us); + if (nb_frame == 0) return; + + for (uint8_t counter = 0; counter < nb_iterations; counter++) { + // Transcode frame and enqueue + a2dp_opus_encode_frames(nb_frame); + } + + return; +} + +// Obtains the number of frames to send and number of iterations +// to be used. |num_of_iterations| and |num_of_frames| parameters +// are used as output param for returning the respective values. +static void a2dp_opus_get_num_frame_iteration(uint8_t* num_of_iterations, + uint8_t* num_of_frames, + uint64_t timestamp_us) { + uint32_t result = 0; + uint8_t nof = 0; + uint8_t noi = 1; + + uint32_t pcm_bytes_per_frame = + a2dp_opus_encoder_cb.opus_encoder_params.framesize * + a2dp_opus_encoder_cb.feeding_params.channel_count * + a2dp_opus_encoder_cb.feeding_params.bits_per_sample / 8; + + uint32_t us_this_tick = a2dp_vendor_opus_get_encoder_interval_ms() * 1000; + uint64_t now_us = timestamp_us; + if (a2dp_opus_encoder_cb.opus_feeding_state.last_frame_us != 0) + us_this_tick = + (now_us - a2dp_opus_encoder_cb.opus_feeding_state.last_frame_us); + a2dp_opus_encoder_cb.opus_feeding_state.last_frame_us = now_us; + + a2dp_opus_encoder_cb.opus_feeding_state.counter += + (float)a2dp_opus_encoder_cb.opus_feeding_state.bytes_per_tick * + us_this_tick / (a2dp_vendor_opus_get_encoder_interval_ms() * 1000); + + result = + a2dp_opus_encoder_cb.opus_feeding_state.counter / pcm_bytes_per_frame; + a2dp_opus_encoder_cb.opus_feeding_state.counter -= + result * pcm_bytes_per_frame; + nof = result; + + *num_of_frames = nof; + *num_of_iterations = noi; +} + +static void a2dp_opus_encode_frames(uint8_t nb_frame) { + tA2DP_OPUS_ENCODER_PARAMS* p_encoder_params = + &a2dp_opus_encoder_cb.opus_encoder_params; + unsigned char* packet; + uint8_t remain_nb_frame = nb_frame; + uint16_t opus_frame_size = p_encoder_params->framesize; + uint8_t read_buffer[p_encoder_params->framesize * + p_encoder_params->pcm_wlength * + p_encoder_params->channel_mode]; + + int32_t out_frames = 0; + uint32_t written = 0; + + uint32_t bytes_read = 0; + while (nb_frame) { + BT_HDR* p_buf = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE); + p_buf->offset = A2DP_OPUS_OFFSET; + p_buf->len = 0; + p_buf->layer_specific = 0; + a2dp_opus_encoder_cb.stats.media_read_total_expected_packets++; + + do { + // + // Read the PCM data and encode it + // + uint32_t temp_bytes_read = 0; + if (a2dp_opus_read_feeding(read_buffer, &temp_bytes_read)) { + bytes_read += temp_bytes_read; + packet = (unsigned char*)(p_buf + 1) + p_buf->offset + p_buf->len; + + if (a2dp_opus_encoder_cb.opus_handle == NULL) { + LOG_ERROR("invalid OPUS handle"); + a2dp_opus_encoder_cb.stats.media_read_total_dropped_packets++; + osi_free(p_buf); + return; + } + + written = + opus_encode(a2dp_opus_encoder_cb.opus_handle, + (const opus_int16*)&read_buffer[0], opus_frame_size, + packet, (BT_DEFAULT_BUFFER_SIZE - p_buf->offset)); + + if (written <= 0) { + LOG_ERROR("OPUS encoding error"); + a2dp_opus_encoder_cb.stats.media_read_total_dropped_packets++; + osi_free(p_buf); + return; + } else { + out_frames++; + } + p_buf->len += written; + nb_frame--; + p_buf->layer_specific += out_frames; // added a frame to the buffer + } else { + LOG_WARN("Opus src buffer underflow %d", nb_frame); + a2dp_opus_encoder_cb.opus_feeding_state.counter += + nb_frame * opus_frame_size * + a2dp_opus_encoder_cb.feeding_params.channel_count * + a2dp_opus_encoder_cb.feeding_params.bits_per_sample / 8; + + // no more pcm to read + nb_frame = 0; + } + } while ((written == 0) && nb_frame); + + if (p_buf->len) { + /* + * Timestamp of the media packet header represent the TS of the + * first frame, i.e. the timestamp before including this frame. + */ + *((uint32_t*)(p_buf + 1)) = a2dp_opus_encoder_cb.timestamp; + + a2dp_opus_encoder_cb.timestamp += p_buf->layer_specific * opus_frame_size; + + uint8_t done_nb_frame = remain_nb_frame - nb_frame; + remain_nb_frame = nb_frame; + + if (!a2dp_opus_encoder_cb.enqueue_callback(p_buf, done_nb_frame, + bytes_read)) + return; + } else { + a2dp_opus_encoder_cb.stats.media_read_total_dropped_packets++; + osi_free(p_buf); + } + } +} + +static bool a2dp_opus_read_feeding(uint8_t* read_buffer, uint32_t* bytes_read) { + uint32_t read_size = a2dp_opus_encoder_cb.opus_encoder_params.framesize * + a2dp_opus_encoder_cb.feeding_params.channel_count * + a2dp_opus_encoder_cb.feeding_params.bits_per_sample / 8; + + a2dp_opus_encoder_cb.stats.media_read_total_expected_reads_count++; + a2dp_opus_encoder_cb.stats.media_read_total_expected_read_bytes += read_size; + + /* Read Data from UIPC channel */ + uint32_t nb_byte_read = + a2dp_opus_encoder_cb.read_callback(read_buffer, read_size); + a2dp_opus_encoder_cb.stats.media_read_total_actual_read_bytes += nb_byte_read; + + if (nb_byte_read < read_size) { + if (nb_byte_read == 0) return false; + + /* Fill the unfilled part of the read buffer with silence (0) */ + memset(((uint8_t*)read_buffer) + nb_byte_read, 0, read_size - nb_byte_read); + nb_byte_read = read_size; + } + a2dp_opus_encoder_cb.stats.media_read_total_actual_reads_count++; + + *bytes_read = nb_byte_read; + return true; +} + +void a2dp_vendor_opus_set_transmit_queue_length(size_t transmit_queue_length) { + a2dp_opus_encoder_cb.TxQueueLength = transmit_queue_length; + + return; +} + +uint64_t A2dpCodecConfigOpusSource::encoderIntervalMs() const { + return a2dp_vendor_opus_get_encoder_interval_ms(); +} + +int a2dp_vendor_opus_get_effective_frame_size() { + return a2dp_opus_encoder_cb.TxAaMtuSize; +} + +void A2dpCodecConfigOpusSource::debug_codec_dump(int fd) { + a2dp_opus_encoder_stats_t* stats = &a2dp_opus_encoder_cb.stats; + tA2DP_OPUS_ENCODER_PARAMS* p_encoder_params = + &a2dp_opus_encoder_cb.opus_encoder_params; + + A2dpCodecConfig::debug_codec_dump(fd); + + dprintf(fd, + " Packet counts (expected/dropped) : %zu / " + "%zu\n", + stats->media_read_total_expected_packets, + stats->media_read_total_dropped_packets); + + dprintf(fd, + " PCM read counts (expected/actual) : %zu / " + "%zu\n", + stats->media_read_total_expected_reads_count, + stats->media_read_total_actual_reads_count); + + dprintf(fd, + " PCM read bytes (expected/actual) : %zu / " + "%zu\n", + stats->media_read_total_expected_read_bytes, + stats->media_read_total_actual_read_bytes); + + dprintf(fd, + " OPUS transmission bitrate (Kbps) : %d\n", + p_encoder_params->bitrate); + + dprintf(fd, + " OPUS saved transmit queue length : %zu\n", + a2dp_opus_encoder_cb.TxQueueLength); + + return; +} diff --git a/system/stack/include/a2dp_error_codes.h b/system/stack/include/a2dp_error_codes.h index 0aa7105b7ac..ae5e26acf5f 100644 --- a/system/stack/include/a2dp_error_codes.h +++ b/system/stack/include/a2dp_error_codes.h @@ -128,6 +128,9 @@ */ #define A2DP_BAD_CP_FORMAT 0xE1 +/* Invalid framesize */ +#define A2DP_NS_FRAMESIZE 0xE2 + typedef uint8_t tA2DP_STATUS; #endif // A2DP_ERROR_CODES_H diff --git a/system/stack/include/a2dp_vendor_opus.h b/system/stack/include/a2dp_vendor_opus.h new file mode 100644 index 00000000000..08a0b7b660f --- /dev/null +++ b/system/stack/include/a2dp_vendor_opus.h @@ -0,0 +1,255 @@ +/* + * Copyright 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. + */ + +// +// A2DP Codec API for Opus +// + +#ifndef A2DP_VENDOR_OPUS_H +#define A2DP_VENDOR_OPUS_H + +#include "a2dp_codec_api.h" +#include "a2dp_vendor_opus_constants.h" +#include "avdt_api.h" + +class A2dpCodecConfigOpusBase : public A2dpCodecConfig { + protected: + A2dpCodecConfigOpusBase(btav_a2dp_codec_index_t codec_index, + const std::string& name, + btav_a2dp_codec_priority_t codec_priority, + bool is_source) + : A2dpCodecConfig(codec_index, name, codec_priority), + is_source_(is_source) {} + bool setCodecConfig(const uint8_t* p_peer_codec_info, bool is_capability, + uint8_t* p_result_codec_config) override; + bool setPeerCodecCapabilities( + const uint8_t* p_peer_codec_capabilities) override; + + private: + bool is_source_; // True if local is Source +}; + +class A2dpCodecConfigOpusSource : public A2dpCodecConfigOpusBase { + public: + A2dpCodecConfigOpusSource(btav_a2dp_codec_priority_t codec_priority); + virtual ~A2dpCodecConfigOpusSource(); + + bool init() override; + uint64_t encoderIntervalMs() const; + + private: + bool useRtpHeaderMarkerBit() const override; + bool updateEncoderUserConfig( + const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, + bool* p_restart_input, bool* p_restart_output, bool* p_config_updated); + void debug_codec_dump(int fd) override; +}; + +class A2dpCodecConfigOpusSink : public A2dpCodecConfigOpusBase { + public: + A2dpCodecConfigOpusSink(btav_a2dp_codec_priority_t codec_priority); + virtual ~A2dpCodecConfigOpusSink(); + + bool init() override; + uint64_t encoderIntervalMs() const; + + private: + bool useRtpHeaderMarkerBit() const override; + bool updateEncoderUserConfig( + const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, + bool* p_restart_input, bool* p_restart_output, bool* p_config_updated); +}; + +// Checks whether the codec capabilities contain a valid A2DP Opus Source +// codec. +// NOTE: only codecs that are implemented are considered valid. +// Returns true if |p_codec_info| contains information about a valid Opus +// codec, otherwise false. +bool A2DP_IsVendorSourceCodecValidOpus(const uint8_t* p_codec_info); + +// Checks whether the codec capabilities contain a valid A2DP Opus Sink +// codec. +// NOTE: only codecs that are implemented are considered valid. +// Returns true if |p_codec_info| contains information about a valid Opus +// codec, otherwise false. +bool A2DP_IsVendorSinkCodecValidOpus(const uint8_t* p_codec_info); + +// Checks whether the codec capabilities contain a valid peer A2DP Opus Sink +// codec. +// NOTE: only codecs that are implemented are considered valid. +// Returns true if |p_codec_info| contains information about a valid Opus +// codec, otherwise false. +bool A2DP_IsVendorPeerSinkCodecValidOpus(const uint8_t* p_codec_info); + +// Checks whether the codec capabilities contain a valid peer A2DP Opus Source +// codec. +// NOTE: only codecs that are implemented are considered valid. +// Returns true if |p_codec_info| contains information about a valid Opus +// codec, otherwise false. +bool A2DP_IsVendorPeerSourceCodecValidOpus(const uint8_t* p_codec_info); + +// Checks whether A2DP Opus Sink codec is supported. +// |p_codec_info| contains information about the codec capabilities. +// Returns true if the A2DP Opus Sink codec is supported, otherwise false. +bool A2DP_IsVendorSinkCodecSupportedOpus(const uint8_t* p_codec_info); + +// Checks whether an A2DP Opus Source codec for a peer Source device is +// supported. +// |p_codec_info| contains information about the codec capabilities of the +// peer device. +// Returns true if the A2DP Opus Source codec for a peer Source device is +// supported, otherwise false. +bool A2DP_IsPeerSourceCodecSupportedOpus(const uint8_t* p_codec_info); + +// Checks whether the A2DP data packets should contain RTP header. +// |content_protection_enabled| is true if Content Protection is +// enabled. |p_codec_info| contains information about the codec capabilities. +// Returns true if the A2DP data packets should contain RTP header, otherwise +// false. +bool A2DP_VendorUsesRtpHeaderOpus(bool content_protection_enabled, + const uint8_t* p_codec_info); + +// Gets the A2DP Opus codec name for a given |p_codec_info|. +const char* A2DP_VendorCodecNameOpus(const uint8_t* p_codec_info); + +// Checks whether two A2DP Opus codecs |p_codec_info_a| and |p_codec_info_b| +// have the same type. +// Returns true if the two codecs have the same type, otherwise false. +bool A2DP_VendorCodecTypeEqualsOpus(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b); + +// Checks whether two A2DP Opus codecs |p_codec_info_a| and |p_codec_info_b| +// are exactly the same. +// Returns true if the two codecs are exactly the same, otherwise false. +// If the codec type is not Opus, the return value is false. +bool A2DP_VendorCodecEqualsOpus(const uint8_t* p_codec_info_a, + const uint8_t* p_codec_info_b); + +// Gets the track sample rate value for the A2DP Opus codec. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the track sample rate on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetTrackSampleRateOpus(const uint8_t* p_codec_info); + +// Gets the track bits per sample value for the A2DP Opus codec. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the track bits per sample on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetTrackBitsPerSampleOpus(const uint8_t* p_codec_info); + +// Gets the track bitrate value for the A2DP Opus codec. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the track sample rate on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetBitRateOpus(const uint8_t* p_codec_info); + +// Gets the channel count for the A2DP Opus codec. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the channel count on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetTrackChannelCountOpus(const uint8_t* p_codec_info); + +// Gets the channel type for the A2DP Opus codec. +// 1 for mono, or 3 for dual channel/stereo. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the channel count on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetSinkTrackChannelTypeOpus(const uint8_t* p_codec_info); + +// Gets the channel mode code for the A2DP Opus codec. +// The actual value is codec-specific - see |A2DP_OPUS_CHANNEL_MODE_*|. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the channel mode code on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetChannelModeCodeOpus(const uint8_t* p_codec_info); + +// Gets the framesize value (in ms) for the A2DP Opus codec. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns the framesize on success, or -1 if |p_codec_info| +// contains invalid codec information. +int A2DP_VendorGetFrameSizeOpus(const uint8_t* p_codec_info); + +// Gets the A2DP Opus audio data timestamp from an audio packet. +// |p_codec_info| contains the codec information. +// |p_data| contains the audio data. +// The timestamp is stored in |p_timestamp|. +// Returns true on success, otherwise false. +bool A2DP_VendorGetPacketTimestampOpus(const uint8_t* p_codec_info, + const uint8_t* p_data, + uint32_t* p_timestamp); + +// Builds A2DP Opus codec header for audio data. +// |p_codec_info| contains the codec information. +// |p_buf| contains the audio data. +// |frames_per_packet| is the number of frames in this packet. +// Returns true on success, otherwise false. +bool A2DP_VendorBuildCodecHeaderOpus(const uint8_t* p_codec_info, BT_HDR* p_buf, + uint16_t frames_per_packet); + +// Decodes A2DP Opus codec info into a human readable string. +// |p_codec_info| is a pointer to the Opus codec_info to decode. +// Returns a string describing the codec information. +std::string A2DP_VendorCodecInfoStringOpus(const uint8_t* p_codec_info); + +// Gets the A2DP Opus encoder interface that can be used to encode and prepare +// A2DP packets for transmission - see |tA2DP_ENCODER_INTERFACE|. +// |p_codec_info| contains the codec information. +// Returns the A2DP Opus encoder interface if the |p_codec_info| is valid and +// supported, otherwise NULL. +const tA2DP_ENCODER_INTERFACE* A2DP_VendorGetEncoderInterfaceOpus( + const uint8_t* p_codec_info); + +// Gets the current A2DP Opus decoder interface that can be used to decode +// received A2DP packets - see |tA2DP_DECODER_INTERFACE|. +// |p_codec_info| contains the codec information. +// Returns the A2DP Opus decoder interface if the |p_codec_info| is valid and +// supported, otherwise NULL. +const tA2DP_DECODER_INTERFACE* A2DP_VendorGetDecoderInterfaceOpus( + const uint8_t* p_codec_info); + +// Adjusts the A2DP Opus codec, based on local support and Bluetooth +// specification. +// |p_codec_info| contains the codec information to adjust. +// Returns true if |p_codec_info| is valid and supported, otherwise false. +bool A2DP_VendorAdjustCodecOpus(uint8_t* p_codec_info); + +// Gets the A2DP Opus Source codec index for a given |p_codec_info|. +// Returns the corresponding |btav_a2dp_codec_index_t| on success, +// otherwise |BTAV_A2DP_CODEC_INDEX_MAX|. +btav_a2dp_codec_index_t A2DP_VendorSourceCodecIndexOpus( + const uint8_t* p_codec_info); + +// Gets the A2DP Opus Sink codec index for a given |p_codec_info|. +// Returns the corresponding |btav_a2dp_codec_index_t| on success, +// otherwise |BTAV_A2DP_CODEC_INDEX_MAX|. +btav_a2dp_codec_index_t A2DP_VendorSinkCodecIndexOpus( + const uint8_t* p_codec_info); + +// Gets the A2DP Opus Source codec name. +const char* A2DP_VendorCodecIndexStrOpus(void); + +// Gets the A2DP Opus Sink codec name. +const char* A2DP_VendorCodecIndexStrOpusSink(void); + +// Initializes A2DP Opus Source codec information into |AvdtpSepConfig| +// configuration entry pointed by |p_cfg|. +bool A2DP_VendorInitCodecConfigOpus(AvdtpSepConfig* p_cfg); + +// Initializes A2DP Opus Sink codec information into |AvdtpSepConfig| +// configuration entry pointed by |p_cfg|. +bool A2DP_VendorInitCodecConfigOpusSink(AvdtpSepConfig* p_cfg); + +#endif // A2DP_VENDOR_OPUS_H diff --git a/system/stack/include/a2dp_vendor_opus_constants.h b/system/stack/include/a2dp_vendor_opus_constants.h new file mode 100644 index 00000000000..272b04c48d3 --- /dev/null +++ b/system/stack/include/a2dp_vendor_opus_constants.h @@ -0,0 +1,66 @@ +/* + * Copyright 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. + */ + +// +// A2DP constants for Opus codec +// + +#ifndef A2DP_VENDOR_OPUS_CONSTANTS_H +#define A2DP_VENDOR_OPUS_CONSTANTS_H + +#define A2DP_OPUS_CODEC_LEN 9 + +#define A2DP_OPUS_CODEC_OUTPUT_CHS 2 +#define A2DP_OPUS_CODEC_DEFAULT_SAMPLERATE 48000 +#define A2DP_OPUS_CODEC_DEFAULT_FRAMESIZE 960 +#define A2DP_OPUS_DECODE_BUFFER_LENGTH \ + (A2DP_OPUS_CODEC_OUTPUT_CHS * A2DP_OPUS_CODEC_DEFAULT_FRAMESIZE * 4) + +// [Octet 0-3] Vendor ID +#define A2DP_OPUS_VENDOR_ID 0x000000E0 +// [Octet 4-5] Vendor Specific Codec ID +#define A2DP_OPUS_CODEC_ID 0x0001 +// [Octet 6], [Bits 0,1,2] Channel Mode +#define A2DP_OPUS_CHANNEL_MODE_MASK 0x07 +#define A2DP_OPUS_CHANNEL_MODE_MONO 0x01 +#define A2DP_OPUS_CHANNEL_MODE_STEREO 0x02 +#define A2DP_OPUS_CHANNEL_MODE_DUAL_MONO 0x04 +// [Octet 6], [Bits 3,4] Future 2, FrameSize +#define A2DP_OPUS_FRAMESIZE_MASK 0x18 +#define A2DP_OPUS_10MS_FRAMESIZE 0x08 +#define A2DP_OPUS_20MS_FRAMESIZE 0x10 +// [Octet 6], [Bits 5] Sampling Frequency +#define A2DP_OPUS_SAMPLING_FREQ_MASK 0x80 +#define A2DP_OPUS_SAMPLING_FREQ_48000 0x80 +// [Octet 6], [Bits 6,7] Reserved +#define A2DP_OPUS_FUTURE_3 0x40 +#define A2DP_OPUS_FUTURE_4 0x80 + +// Length of the Opus Media Payload header +#define A2DP_OPUS_MPL_HDR_LEN 1 + +#if (BTA_AV_CO_CP_SCMS_T == TRUE) +#define A2DP_OPUS_OFFSET (AVDT_MEDIA_OFFSET + A2DP_OPUS_MPL_HDR_LEN + 1) +#else +#define A2DP_OPUS_OFFSET (AVDT_MEDIA_OFFSET + A2DP_OPUS_MPL_HDR_LEN) +#endif + +#define A2DP_OPUS_HDR_F_MSK 0x80 +#define A2DP_OPUS_HDR_S_MSK 0x40 +#define A2DP_OPUS_HDR_L_MSK 0x20 +#define A2DP_OPUS_HDR_NUM_MSK 0x0F + +#endif diff --git a/system/stack/include/a2dp_vendor_opus_decoder.h b/system/stack/include/a2dp_vendor_opus_decoder.h new file mode 100644 index 00000000000..67b5bf706f3 --- /dev/null +++ b/system/stack/include/a2dp_vendor_opus_decoder.h @@ -0,0 +1,45 @@ +/* + * Copyright 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. + */ + +// +// Interface to the A2DP Opus Decoder +// + +#ifndef A2DP_VENDOR_OPUS_DECODER_H +#define A2DP_VENDOR_OPUS_DECODER_H + +#include "a2dp_codec_api.h" + +// Initialize the A2DP Opus decoder. +bool a2dp_vendor_opus_decoder_init(decoded_data_callback_t decode_callback); + +// Cleanup the A2DP Opus decoder. +void a2dp_vendor_opus_decoder_cleanup(void); + +// Decodes |p_buf|. Calls |decode_callback| passed into +// |a2dp_vendor_opus_decoder_init| if decoded frames are available. +bool a2dp_vendor_opus_decoder_decode_packet(BT_HDR* p_buf); + +// Start the A2DP Opus decoder. +void a2dp_vendor_opus_decoder_start(void); + +// Suspend the A2DP Opus decoder. +void a2dp_vendor_opus_decoder_suspend(void); + +// A2DP Opus decoder configuration. +void a2dp_vendor_opus_decoder_configure(const uint8_t* p_codec_info); + +#endif // A2DP_VENDOR_OPUS_DECODER_H diff --git a/system/stack/include/a2dp_vendor_opus_encoder.h b/system/stack/include/a2dp_vendor_opus_encoder.h new file mode 100644 index 00000000000..0f88265f1f9 --- /dev/null +++ b/system/stack/include/a2dp_vendor_opus_encoder.h @@ -0,0 +1,59 @@ +/* + * Copyright 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. + */ + +// +// Interface to the A2DP Opus Encoder +// + +#ifndef A2DP_VENDOR_OPUS_ENCODER_H +#define A2DP_VENDOR_OPUS_ENCODER_H + +#include "a2dp_codec_api.h" + +// Initialize the A2DP Opus encoder. +// |p_peer_params| contains the A2DP peer information +// The current A2DP codec config is in |a2dp_codec_config|. +// |read_callback| is the callback for reading the input audio data. +// |enqueue_callback| is the callback for enqueueing the encoded audio data. +void a2dp_vendor_opus_encoder_init( + const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, + A2dpCodecConfig* a2dp_codec_config, + a2dp_source_read_callback_t read_callback, + a2dp_source_enqueue_callback_t enqueue_callback); + +// Cleanup the A2DP Opus encoder. +void a2dp_vendor_opus_encoder_cleanup(void); + +// Reset the feeding for the A2DP Opus encoder. +void a2dp_vendor_opus_feeding_reset(void); + +// Flush the feeding for the A2DP Opus encoder. +void a2dp_vendor_opus_feeding_flush(void); + +// Get the A2DP Opus encoder interval (in milliseconds). +uint64_t a2dp_vendor_opus_get_encoder_interval_ms(void); + +// Prepare and send A2DP Opus encoded frames. +// |timestamp_us| is the current timestamp (in microseconds). +void a2dp_vendor_opus_send_frames(uint64_t timestamp_us); + +// Set transmit queue length for the A2DP Opus (Dynamic Bit Rate) mechanism. +void a2dp_vendor_opus_set_transmit_queue_length(size_t transmit_queue_length); + +// Get the A2DP Opus encoded maximum frame size +int a2dp_vendor_opus_get_effective_frame_size(); + +#endif // A2DP_VENDOR_OPUS_ENCODER_H diff --git a/system/stack/test/fuzzers/Android.bp b/system/stack/test/fuzzers/Android.bp index 54812c0b298..376b5ad5927 100644 --- a/system/stack/test/fuzzers/Android.bp +++ b/system/stack/test/fuzzers/Android.bp @@ -35,6 +35,7 @@ cc_defaults { "libbtdevice", "libg722codec", "liblc3", + "libopus", "libosi", "libudrv-uipc", "libbt-protos-lite", diff --git a/system/stack/test/stack_a2dp_test.cc b/system/stack/test/stack_a2dp_test.cc index fe7692a8a5f..84744211185 100644 --- a/system/stack/test/stack_a2dp_test.cc +++ b/system/stack/test/stack_a2dp_test.cc @@ -27,6 +27,7 @@ #include "stack/include/a2dp_codec_api.h" #include "stack/include/a2dp_sbc.h" #include "stack/include/a2dp_vendor.h" +#include "stack/include/a2dp_vendor_opus_constants.h" #include "stack/include/bt_hdr.h" namespace { @@ -187,6 +188,47 @@ const uint8_t codec_info_aac_sink_capability[AVDT_CODEC_SIZE] = { 9 // Fake }; +const uint8_t codec_info_opus[AVDT_CODEC_SIZE] = { + A2DP_OPUS_CODEC_LEN, // Length + AVDT_MEDIA_TYPE_AUDIO << 4, // Media Type + A2DP_MEDIA_CT_NON_A2DP, // Media Codec Type Vendor + (A2DP_OPUS_VENDOR_ID & 0x000000FF), + (A2DP_OPUS_VENDOR_ID & 0x0000FF00) >> 8, + (A2DP_OPUS_VENDOR_ID & 0x00FF0000) >> 16, + (A2DP_OPUS_VENDOR_ID & 0xFF000000) >> 24, + (A2DP_OPUS_CODEC_ID & 0x00FF), + (A2DP_OPUS_CODEC_ID & 0xFF00) >> 8, + A2DP_OPUS_CHANNEL_MODE_STEREO | A2DP_OPUS_20MS_FRAMESIZE | + A2DP_OPUS_SAMPLING_FREQ_48000}; + +const uint8_t codec_info_opus_capability[AVDT_CODEC_SIZE] = { + A2DP_OPUS_CODEC_LEN, // Length + AVDT_MEDIA_TYPE_AUDIO << 4, // Media Type + A2DP_MEDIA_CT_NON_A2DP, // Media Codec Type Vendor + (A2DP_OPUS_VENDOR_ID & 0x000000FF), + (A2DP_OPUS_VENDOR_ID & 0x0000FF00) >> 8, + (A2DP_OPUS_VENDOR_ID & 0x00FF0000) >> 16, + (A2DP_OPUS_VENDOR_ID & 0xFF000000) >> 24, + (A2DP_OPUS_CODEC_ID & 0x00FF), + (A2DP_OPUS_CODEC_ID & 0xFF00) >> 8, + A2DP_OPUS_CHANNEL_MODE_MONO | A2DP_OPUS_CHANNEL_MODE_STEREO | + A2DP_OPUS_10MS_FRAMESIZE | A2DP_OPUS_20MS_FRAMESIZE | + A2DP_OPUS_SAMPLING_FREQ_48000}; + +const uint8_t codec_info_opus_sink_capability[AVDT_CODEC_SIZE] = { + A2DP_OPUS_CODEC_LEN, // Length + AVDT_MEDIA_TYPE_AUDIO << 4, // Media Type + A2DP_MEDIA_CT_NON_A2DP, // Media Codec Type Vendor + (A2DP_OPUS_VENDOR_ID & 0x000000FF), + (A2DP_OPUS_VENDOR_ID & 0x0000FF00) >> 8, + (A2DP_OPUS_VENDOR_ID & 0x00FF0000) >> 16, + (A2DP_OPUS_VENDOR_ID & 0xFF000000) >> 24, + (A2DP_OPUS_CODEC_ID & 0x00FF), + (A2DP_OPUS_CODEC_ID & 0xFF00) >> 8, + A2DP_OPUS_CHANNEL_MODE_MONO | A2DP_OPUS_CHANNEL_MODE_STEREO | + A2DP_OPUS_10MS_FRAMESIZE | A2DP_OPUS_20MS_FRAMESIZE | + A2DP_OPUS_SAMPLING_FREQ_48000}; + const uint8_t codec_info_non_a2dp[AVDT_CODEC_SIZE] = { 8, // Length 0, // Media Type: AVDT_MEDIA_TYPE_AUDIO @@ -265,10 +307,11 @@ class StackA2dpTest : public ::testing::Test { supported = has_shared_library(LDAC_DECODER_LIB_NAME); break; case BTAV_A2DP_CODEC_INDEX_SOURCE_LC3: + break; case BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS: - // TODO(b/226441860): in-progress case BTAV_A2DP_CODEC_INDEX_SINK_OPUS: - // TODO(b/226441860): in-progress + supported = true; + break; case BTAV_A2DP_CODEC_INDEX_MAX: // Needed to avoid using "default:" case so we can capture when // a new codec is added, and it can be included here. @@ -386,6 +429,32 @@ TEST_F(StackA2dpTest, test_a2dp_is_codec_valid_aac) { EXPECT_FALSE(A2DP_IsPeerSinkCodecValid(codec_info_aac_invalid)); } +TEST_F(StackA2dpTest, test_a2dp_is_codec_valid_opus) { + ASSERT_TRUE(A2DP_IsVendorSourceCodecValid(codec_info_opus)); + ASSERT_TRUE(A2DP_IsVendorSourceCodecValid(codec_info_opus_capability)); + ASSERT_TRUE(A2DP_IsVendorPeerSourceCodecValid(codec_info_opus)); + ASSERT_TRUE(A2DP_IsVendorPeerSourceCodecValid(codec_info_opus_capability)); + + ASSERT_TRUE(A2DP_IsVendorSinkCodecValid(codec_info_opus_sink_capability)); + ASSERT_TRUE(A2DP_IsVendorPeerSinkCodecValid(codec_info_opus_sink_capability)); + + // Test with invalid Opus configuration + uint8_t codec_info_opus_invalid[AVDT_CODEC_SIZE]; + memcpy(codec_info_opus_invalid, codec_info_opus, sizeof(codec_info_opus)); + codec_info_opus_invalid[0] = 0; // Corrupt the Length field + ASSERT_FALSE(A2DP_IsVendorSourceCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorSinkCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorPeerSourceCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorPeerSinkCodecValid(codec_info_opus_invalid)); + + memcpy(codec_info_opus_invalid, codec_info_opus, sizeof(codec_info_opus)); + codec_info_opus_invalid[1] = 0xff; // Corrupt the Media Type field + ASSERT_FALSE(A2DP_IsVendorSourceCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorSinkCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorPeerSourceCodecValid(codec_info_opus_invalid)); + ASSERT_FALSE(A2DP_IsVendorPeerSinkCodecValid(codec_info_opus_invalid)); +} + TEST_F(StackA2dpTest, test_a2dp_get_codec_type) { tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(codec_info_sbc); EXPECT_EQ(codec_type, A2DP_MEDIA_CT_SBC); @@ -393,6 +462,9 @@ TEST_F(StackA2dpTest, test_a2dp_get_codec_type) { codec_type = A2DP_GetCodecType(codec_info_aac); EXPECT_EQ(codec_type, A2DP_MEDIA_CT_AAC); + codec_type = A2DP_GetCodecType(codec_info_opus); + ASSERT_EQ(codec_type, A2DP_MEDIA_CT_NON_A2DP); + codec_type = A2DP_GetCodecType(codec_info_non_a2dp); EXPECT_EQ(codec_type, A2DP_MEDIA_CT_NON_A2DP); } @@ -443,6 +515,9 @@ TEST_F(StackA2dpTest, test_a2dp_uses_rtp_header) { EXPECT_TRUE(A2DP_UsesRtpHeader(true, codec_info_aac)); EXPECT_TRUE(A2DP_UsesRtpHeader(false, codec_info_aac)); + ASSERT_TRUE(A2DP_VendorUsesRtpHeader(true, codec_info_opus)); + ASSERT_TRUE(A2DP_VendorUsesRtpHeader(false, codec_info_opus)); + EXPECT_TRUE(A2DP_UsesRtpHeader(true, codec_info_non_a2dp)); EXPECT_TRUE(A2DP_UsesRtpHeader(false, codec_info_non_a2dp)); } @@ -473,6 +548,9 @@ TEST_F(StackA2dpTest, test_a2dp_codec_name) { EXPECT_STREQ(A2DP_CodecName(codec_info_aac), "AAC"); EXPECT_STREQ(A2DP_CodecName(codec_info_aac_capability), "AAC"); EXPECT_STREQ(A2DP_CodecName(codec_info_aac_sink_capability), "AAC"); + ASSERT_STREQ(A2DP_CodecName(codec_info_opus), "Opus"); + ASSERT_STREQ(A2DP_CodecName(codec_info_opus_capability), "Opus"); + ASSERT_STREQ(A2DP_CodecName(codec_info_opus_sink_capability), "Opus"); EXPECT_STREQ(A2DP_CodecName(codec_info_non_a2dp), "UNKNOWN VENDOR CODEC"); // Test all unknown codecs @@ -496,11 +574,19 @@ TEST_F(StackA2dpTest, test_a2dp_codec_type_equals) { EXPECT_TRUE(A2DP_CodecTypeEquals(codec_info_sbc, codec_info_sbc_capability)); EXPECT_TRUE( A2DP_CodecTypeEquals(codec_info_sbc, codec_info_sbc_sink_capability)); + EXPECT_TRUE(A2DP_CodecTypeEquals(codec_info_aac, codec_info_aac_capability)); EXPECT_TRUE( A2DP_CodecTypeEquals(codec_info_aac, codec_info_aac_sink_capability)); + + ASSERT_TRUE( + A2DP_VendorCodecTypeEquals(codec_info_opus, codec_info_opus_capability)); + ASSERT_TRUE(A2DP_VendorCodecTypeEquals(codec_info_opus, + codec_info_opus_sink_capability)); + EXPECT_TRUE( A2DP_CodecTypeEquals(codec_info_non_a2dp, codec_info_non_a2dp_fake)); + EXPECT_FALSE(A2DP_CodecTypeEquals(codec_info_sbc, codec_info_non_a2dp)); EXPECT_FALSE(A2DP_CodecTypeEquals(codec_info_aac, codec_info_non_a2dp)); EXPECT_FALSE(A2DP_CodecTypeEquals(codec_info_sbc, codec_info_aac)); @@ -509,6 +595,7 @@ TEST_F(StackA2dpTest, test_a2dp_codec_type_equals) { TEST_F(StackA2dpTest, test_a2dp_codec_equals) { uint8_t codec_info_sbc_test[AVDT_CODEC_SIZE]; uint8_t codec_info_aac_test[AVDT_CODEC_SIZE]; + uint8_t codec_info_opus_test[AVDT_CODEC_SIZE]; uint8_t codec_info_non_a2dp_test[AVDT_CODEC_SIZE]; // Test two identical SBC codecs @@ -521,6 +608,11 @@ TEST_F(StackA2dpTest, test_a2dp_codec_equals) { memcpy(codec_info_aac_test, codec_info_aac, sizeof(codec_info_aac)); EXPECT_TRUE(A2DP_CodecEquals(codec_info_aac, codec_info_aac_test)); + // Test two identical Opus codecs + memset(codec_info_opus_test, 0xAB, sizeof(codec_info_opus_test)); + memcpy(codec_info_opus_test, codec_info_opus, sizeof(codec_info_opus)); + ASSERT_TRUE(A2DP_VendorCodecEquals(codec_info_opus, codec_info_opus_test)); + // Test two identical non-A2DP codecs that are not recognized memset(codec_info_non_a2dp_test, 0xAB, sizeof(codec_info_non_a2dp_test)); memcpy(codec_info_non_a2dp_test, codec_info_non_a2dp, @@ -529,7 +621,8 @@ TEST_F(StackA2dpTest, test_a2dp_codec_equals) { // Test two codecs that have different types EXPECT_FALSE(A2DP_CodecEquals(codec_info_sbc, codec_info_non_a2dp)); - EXPECT_FALSE(A2DP_CodecEquals(codec_info_sbc, codec_info_aac)); + ASSERT_FALSE(A2DP_CodecEquals(codec_info_sbc, codec_info_aac)); + ASSERT_FALSE(A2DP_CodecEquals(codec_info_sbc, codec_info_opus)); // Test two SBC codecs that are slightly different memset(codec_info_sbc_test, 0xAB, sizeof(codec_info_sbc_test)); @@ -567,12 +660,14 @@ TEST_F(StackA2dpTest, test_a2dp_codec_equals) { TEST_F(StackA2dpTest, test_a2dp_get_track_sample_rate) { EXPECT_EQ(A2DP_GetTrackSampleRate(codec_info_sbc), 44100); EXPECT_EQ(A2DP_GetTrackSampleRate(codec_info_aac), 44100); + ASSERT_EQ(A2DP_VendorGetTrackSampleRate(codec_info_opus), 48000); EXPECT_EQ(A2DP_GetTrackSampleRate(codec_info_non_a2dp), -1); } TEST_F(StackA2dpTest, test_a2dp_get_track_channel_count) { EXPECT_EQ(A2DP_GetTrackChannelCount(codec_info_sbc), 2); EXPECT_EQ(A2DP_GetTrackChannelCount(codec_info_aac), 2); + ASSERT_EQ(A2DP_VendorGetTrackChannelCount(codec_info_opus), 2); EXPECT_EQ(A2DP_GetTrackChannelCount(codec_info_non_a2dp), -1); } @@ -625,6 +720,7 @@ TEST_F(StackA2dpTest, test_a2dp_get_max_bitpool_sbc) { TEST_F(StackA2dpTest, test_a2dp_get_sink_track_channel_type) { EXPECT_EQ(A2DP_GetSinkTrackChannelType(codec_info_sbc), 3); EXPECT_EQ(A2DP_GetSinkTrackChannelType(codec_info_aac), 3); + ASSERT_EQ(A2DP_VendorGetSinkTrackChannelType(codec_info_opus), 2); EXPECT_EQ(A2DP_GetSinkTrackChannelType(codec_info_non_a2dp), -1); } @@ -669,6 +765,13 @@ TEST_F(StackA2dpTest, test_a2dp_get_packet_timestamp) { EXPECT_TRUE(A2DP_GetPacketTimestamp(codec_info_aac, a2dp_data, ×tamp)); EXPECT_EQ(timestamp, static_cast(0x12345678)); + memset(a2dp_data, 0xAB, sizeof(a2dp_data)); + *p_ts = 0x12345678; + timestamp = 0xFFFFFFFF; + ASSERT_TRUE( + A2DP_VendorGetPacketTimestamp(codec_info_opus, a2dp_data, ×tamp)); + ASSERT_EQ(timestamp, static_cast(0x12345678)); + memset(a2dp_data, 0xAB, sizeof(a2dp_data)); *p_ts = 0x12345678; timestamp = 0xFFFFFFFF; @@ -761,6 +864,12 @@ TEST_F(StackA2dpTest, test_a2dp_source_codec_index) { BTAV_A2DP_CODEC_INDEX_SOURCE_AAC); EXPECT_EQ(A2DP_SourceCodecIndex(codec_info_aac_sink_capability), BTAV_A2DP_CODEC_INDEX_SOURCE_AAC); + ASSERT_EQ(A2DP_VendorSourceCodecIndex(codec_info_opus), + BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS); + ASSERT_EQ(A2DP_VendorSourceCodecIndex(codec_info_opus_capability), + BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS); + ASSERT_EQ(A2DP_VendorSourceCodecIndex(codec_info_opus_sink_capability), + BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS); EXPECT_EQ(A2DP_SourceCodecIndex(codec_info_non_a2dp), BTAV_A2DP_CODEC_INDEX_MAX); } @@ -779,6 +888,12 @@ TEST_F(StackA2dpTest, test_a2dp_sink_codec_index) { BTAV_A2DP_CODEC_INDEX_SINK_AAC); EXPECT_EQ(A2DP_SinkCodecIndex(codec_info_aac_sink_capability), BTAV_A2DP_CODEC_INDEX_SINK_AAC); + ASSERT_EQ(A2DP_VendorSinkCodecIndex(codec_info_opus), + BTAV_A2DP_CODEC_INDEX_SINK_OPUS); + ASSERT_EQ(A2DP_VendorSinkCodecIndex(codec_info_opus_capability), + BTAV_A2DP_CODEC_INDEX_SINK_OPUS); + ASSERT_EQ(A2DP_VendorSinkCodecIndex(codec_info_opus_sink_capability), + BTAV_A2DP_CODEC_INDEX_SINK_OPUS); EXPECT_EQ(A2DP_SinkCodecIndex(codec_info_non_a2dp), BTAV_A2DP_CODEC_INDEX_MAX); } @@ -788,6 +903,10 @@ TEST_F(StackA2dpTest, test_a2dp_codec_index_str) { EXPECT_STREQ(A2DP_CodecIndexStr(BTAV_A2DP_CODEC_INDEX_SOURCE_SBC), "SBC"); EXPECT_STREQ(A2DP_CodecIndexStr(BTAV_A2DP_CODEC_INDEX_SINK_SBC), "SBC SINK"); EXPECT_STREQ(A2DP_CodecIndexStr(BTAV_A2DP_CODEC_INDEX_SOURCE_AAC), "AAC"); + ASSERT_STREQ(A2DP_VendorCodecIndexStr(BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS), + "Opus"); + ASSERT_STREQ(A2DP_VendorCodecIndexStr(BTAV_A2DP_CODEC_INDEX_SINK_OPUS), + "Opus SINK"); // Test that the unknown codec string has not changed EXPECT_STREQ(A2DP_CodecIndexStr(BTAV_A2DP_CODEC_INDEX_MAX), diff --git a/system/test/headless/Android.bp b/system/test/headless/Android.bp index 92c6302ccf8..5e009487acf 100644 --- a/system/test/headless/Android.bp +++ b/system/test/headless/Android.bp @@ -66,6 +66,7 @@ cc_test { "libFraunhoferAAC", "libg722codec", "liblc3", + "libopus", "libosi", "libprotobuf-cpp-lite", "libudrv-uipc", diff --git a/system/test/suite/Android.bp b/system/test/suite/Android.bp index c39b1eb2048..30f6f20c0bc 100644 --- a/system/test/suite/Android.bp +++ b/system/test/suite/Android.bp @@ -90,6 +90,7 @@ cc_defaults { "libg722codec", "libgmock", "liblc3", + "libopus", "libosi", "libstatslog_bt", "libc++fs", -- GitLab From f4aa35adf8ed2e06a3d1273c18d3a3561644e0a4 Mon Sep 17 00:00:00 2001 From: Rahul Sabnis Date: Tue, 9 Aug 2022 19:03:11 +0000 Subject: [PATCH 068/998] Reconfigure Address policy on last bond removed Bug: 237572866 Bug: 195410559 Test: system/gd/cert/run --clean --sl4a OobPairingSl4aTest Test: system/gd/cert/run --clean --sl4a_sl4a OobPairingTest Tag: #refactor Ignore-AOSP-First: Security fix Merged-In: Id1c22953c3921810d708e4aee7a8cfc0c4f97718 Change-Id: Id1c22953c3921810d708e4aee7a8cfc0c4f97718 (cherry picked from commit 66bfae6d32c9863431b12a5dbb5817e0efe6f0a8) --- system/bta/dm/bta_dm_act.cc | 21 ++++++++++++++------- system/bta/dm/bta_dm_api.cc | 14 ++++++++++++++ system/bta/dm/bta_dm_int.h | 2 ++ system/bta/include/bta_api.h | 11 +++++++++++ system/btif/src/btif_storage.cc | 12 +++++++++++- system/gd/hci/le_address_manager.cc | 9 +++++++++ system/main/shim/btm_api.cc | 6 ++++++ system/main/shim/btm_api.h | 9 +++++++++ system/stack/btm/btm_ble.cc | 13 ++++++++++++- system/stack/btm/btm_ble_gap.cc | 2 +- system/test/mock/mock_bta_dm_act.h | 9 +++++++++ system/test/mock/mock_main_shim_btm_api.cc | 5 +++++ 12 files changed, 103 insertions(+), 10 deletions(-) diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc index 33837430f54..55f7871c9e3 100644 --- a/system/bta/dm/bta_dm_act.cc +++ b/system/bta/dm/bta_dm_act.cc @@ -652,13 +652,6 @@ void bta_dm_remove_device(const RawAddress& bd_addr) { if (!other_address_connected && !other_address.IsEmpty()) { bta_dm_process_remove_device(other_address); } - - /* Check the length of the paired devices, and if 0 then reset IRK */ - auto paired_devices = btif_config_get_paired_devices(); - if (paired_devices.empty()) { - LOG_INFO("Last paired device removed, resetting IRK"); - btm_ble_reset_id(); - } } /******************************************************************************* @@ -4021,6 +4014,20 @@ void bta_dm_clear_event_filter(void) { bluetooth::shim::BTM_ClearEventFilter(); } +/******************************************************************************* + * + * Function bta_dm_ble_reset_id + * + * Description Reset the local adapter BLE keys. + * + * Parameters: + * + ******************************************************************************/ +void bta_dm_ble_reset_id(void) { + VLOG(1) << "bta_dm_ble_reset_id in bta_dm_act"; + bluetooth::shim::BTM_BleResetId(); +} + /******************************************************************************* * * Function bta_dm_gattc_callback diff --git a/system/bta/dm/bta_dm_api.cc b/system/bta/dm/bta_dm_api.cc index fa15a7e2217..eded208dc74 100644 --- a/system/bta/dm/bta_dm_api.cc +++ b/system/bta/dm/bta_dm_api.cc @@ -675,3 +675,17 @@ void BTA_DmClearEventFilter(void) { APPL_TRACE_API("BTA_DmClearEventFilter"); do_in_main_thread(FROM_HERE, base::Bind(bta_dm_clear_event_filter)); } + +/******************************************************************************* + * + * Function BTA_DmBleResetId + * + * Description This function resets the ble keys such as IRK + * + * Returns void + * + ******************************************************************************/ +void BTA_DmBleResetId(void) { + APPL_TRACE_API("BTA_DmBleResetId"); + do_in_main_thread(FROM_HERE, base::Bind(bta_dm_ble_reset_id)); +} diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h index eaf2fa3882a..d952dccea26 100644 --- a/system/bta/dm/bta_dm_int.h +++ b/system/bta/dm/bta_dm_int.h @@ -543,6 +543,8 @@ extern tBTA_DM_PEER_DEVICE* bta_dm_find_peer_device( extern void bta_dm_clear_event_filter(void); +extern void bta_dm_ble_reset_id(void); + uint8_t bta_dm_search_get_state(); void bta_dm_search_set_state(uint8_t state); diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h index 447823a6a85..59d6d9805eb 100644 --- a/system/bta/include/bta_api.h +++ b/system/bta/include/bta_api.h @@ -1203,4 +1203,15 @@ extern void BTA_VendorInit(void); ******************************************************************************/ extern void BTA_DmClearEventFilter(void); +/******************************************************************************* + * + * Function BTA_DmBleResetId + * + * Description This function resets the ble keys such as IRK + * + * Returns void + * + ******************************************************************************/ +extern void BTA_DmBleResetId(void); + #endif /* BTA_API_H */ diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc index 865bcf9508e..bd7e89c8b5e 100644 --- a/system/btif/src/btif_storage.cc +++ b/system/btif/src/btif_storage.cc @@ -896,6 +896,13 @@ bt_status_t btif_storage_remove_bonded_device( /* write bonded info immediately */ btif_config_flush(); + + /* Check the length of the paired devices, and if 0 then reset IRK */ + auto paired_devices = btif_config_get_paired_devices(); + if (paired_devices.empty()) { + LOG_INFO("Last paired device removed, resetting IRK"); + BTA_DmBleResetId(); + } return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; } @@ -1279,7 +1286,10 @@ bt_status_t btif_storage_add_ble_local_key(const Octet16& key, return BT_STATUS_FAIL; } int ret = btif_config_set_bin("Adapter", name, key.data(), key.size()); - btif_config_save(); + // Had to change this to flush to get it to work on test. + // Seems to work in the real world on a phone... but not sure why there's a + // race in test. Investigate b/239828132 + btif_config_flush(); return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; } diff --git a/system/gd/hci/le_address_manager.cc b/system/gd/hci/le_address_manager.cc index 28f51004a78..3271d16a396 100644 --- a/system/gd/hci/le_address_manager.cc +++ b/system/gd/hci/le_address_manager.cc @@ -51,6 +51,15 @@ void LeAddressManager::SetPrivacyPolicyForInitiatorAddress( bool supports_ble_privacy, std::chrono::milliseconds minimum_rotation_time, std::chrono::milliseconds maximum_rotation_time) { + // Need to update some parameteres like IRK + if (supports_ble_privacy && address_policy_ != AddressPolicy::POLICY_NOT_SET) { + LOG_INFO("Updating rotation parameters."); + rotation_irk_ = rotation_irk; + minimum_rotation_time_ = minimum_rotation_time; + maximum_rotation_time_ = maximum_rotation_time; + set_random_address(); + return; + } ASSERT(address_policy_ == AddressPolicy::POLICY_NOT_SET); ASSERT(address_policy != AddressPolicy::POLICY_NOT_SET); ASSERT_LOG(registered_clients_.empty(), "Policy must be set before clients are registered."); diff --git a/system/main/shim/btm_api.cc b/system/main/shim/btm_api.cc index 6e799313f3c..8b761c0138e 100644 --- a/system/main/shim/btm_api.cc +++ b/system/main/shim/btm_api.cc @@ -37,6 +37,7 @@ #include "main/shim/shim.h" #include "main/shim/stack.h" #include "osi/include/allocator.h" +#include "stack/btm/btm_ble_int.h" #include "stack/btm/btm_int_types.h" #include "stack/include/bt_hdr.h" #include "stack/include/bt_octets.h" @@ -1334,3 +1335,8 @@ tBTM_STATUS bluetooth::shim::BTM_ClearEventFilter() { controller_get_interface()->clear_event_filter(); return BTM_SUCCESS; } + +tBTM_STATUS bluetooth::shim::BTM_BleResetId() { + btm_ble_reset_id(); + return BTM_SUCCESS; +} diff --git a/system/main/shim/btm_api.h b/system/main/shim/btm_api.h index c1dd4be659a..1e10849d1f9 100644 --- a/system/main/shim/btm_api.h +++ b/system/main/shim/btm_api.h @@ -1821,6 +1821,15 @@ tBTM_STATUS BTM_BleGetEnergyInfo(tBTM_BLE_ENERGY_INFO_CBACK* p_ener_cback); ******************************************************************************/ tBTM_STATUS BTM_ClearEventFilter(void); +/******************************************************************************* + * + * Function BTM_BleResetId + * + * Description Resets the local BLE keys + * + *******************************************************************************/ +tBTM_STATUS BTM_BleResetId(void); + /** * Send remote name request to GD shim Name module */ diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc index 9d313b3ea84..3a18f258da9 100644 --- a/system/stack/btm/btm_ble.cc +++ b/system/stack/btm/btm_ble.cc @@ -32,6 +32,7 @@ #include "main/shim/l2c_api.h" #include "main/shim/shim.h" #include "osi/include/allocator.h" +#include "osi/include/properties.h" #include "stack/btm/btm_dev.h" #include "stack/btm/btm_int_types.h" #include "stack/btm/security_device_record.h" @@ -56,6 +57,11 @@ extern bool btm_ble_init_pseudo_addr(tBTM_SEC_DEV_REC* p_dev_rec, extern void gatt_notify_phy_updated(tGATT_STATUS status, uint16_t handle, uint8_t tx_phy, uint8_t rx_phy); + +#ifndef PROPERTY_BLE_PRIVACY_ENABLED +#define PROPERTY_BLE_PRIVACY_ENABLED "bluetooth.core.gap.le.privacy.enabled" +#endif + /******************************************************************************/ /* External Function to be called by other modules */ /******************************************************************************/ @@ -82,7 +88,7 @@ void BTM_SecAddBleDevice(const RawAddress& bd_addr, tBT_DEVICE_TYPE dev_type, p_dev_rec->conn_params.peripheral_latency = BTM_BLE_CONN_PARAM_UNDEF; LOG_DEBUG("Device added, handle=0x%x, p_dev_rec=%p, bd_addr=%s", - p_dev_rec->ble_hci_handle, p_dev_rec, bd_addr.ToString().c_str()); + p_dev_rec->ble_hci_handle, p_dev_rec, PRIVATE_ADDRESS(bd_addr)); } memset(p_dev_rec->sec_bd_name, 0, sizeof(tBTM_BD_NAME)); @@ -2049,6 +2055,11 @@ static void btm_ble_reset_id_impl(const Octet16& rand1, const Octet16& rand2) { /* proceed generate ER */ btm_cb.devcb.ble_encryption_key_value = rand2; btm_notify_new_key(BTM_BLE_KEY_TYPE_ER); + + /* if privacy is enabled, update the irk and RPA in the LE address manager */ + if (btm_cb.ble_ctr_cb.privacy_mode != BTM_PRIVACY_NONE) { + BTM_BleConfigPrivacy(true); + } } struct reset_id_data { diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc index 76f6c6f62ca..c2fda917657 100644 --- a/system/stack/btm/btm_ble_gap.cc +++ b/system/stack/btm/btm_ble_gap.cc @@ -805,7 +805,7 @@ bool BTM_BleConfigPrivacy(bool privacy_mode) { GAP_BleAttrDBUpdate(GATT_UUID_GAP_CENTRAL_ADDR_RESOL, &gap_ble_attr_value); - bluetooth::shim::ACL_ConfigureLePrivacy(privacy_mode); + bluetooth::shim::ACL_ConfigureLePrivacy(privacy_mode); return true; } diff --git a/system/test/mock/mock_bta_dm_act.h b/system/test/mock/mock_bta_dm_act.h index 5a566dcb7de..af586006e61 100644 --- a/system/test/mock/mock_bta_dm_act.h +++ b/system/test/mock/mock_bta_dm_act.h @@ -264,6 +264,15 @@ struct bta_dm_clear_event_filter { }; extern struct bta_dm_clear_event_filter bta_dm_clear_event_filter; +// Name: bta_dm_ble_reset_id +// Params: None +// Return: void +struct bta_dm_ble_reset_id { + std::function body{[]() {}}; + void operator()() { body(); }; +}; +extern struct bta_dm_ble_reset_id bta_dm_ble_reset_id; + // Name: bta_dm_ble_passkey_reply // Params: const RawAddress& bd_addr, bool accept, uint32_t passkey // Return: void diff --git a/system/test/mock/mock_main_shim_btm_api.cc b/system/test/mock/mock_main_shim_btm_api.cc index d799c2027ba..5b21a7618cc 100644 --- a/system/test/mock/mock_main_shim_btm_api.cc +++ b/system/test/mock/mock_main_shim_btm_api.cc @@ -429,3 +429,8 @@ tBTM_STATUS bluetooth::shim::BTM_ClearEventFilter() { mock_function_count_map[__func__]++; return BTM_SUCCESS; } + +tBTM_STATUS bluetooth::shim::BTM_BleResetId() { + mock_function_count_map[__func__]++; + return BTM_SUCCESS; +} -- GitLab From 2e6fb9235955d54b50de86a583bf796f08fd2946 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Sun, 31 Jul 2022 22:37:53 +0900 Subject: [PATCH 069/998] Add sink only/source only senarios Tag: #feature Bug: 238588139 Test: atest bluetooth_le_audio_client_test Change-Id: Ic57a2f545709ec05e858a33cb9ad24b1e3ddfdaf Merged-In: Ic57a2f545709ec05e858a33cb9ad24b1e3ddfdaf (cherry picked from commit 6d6f74136c4ac929909bbbbf332e7be73d55288a) --- .../le_audio/audio_set_configurations.json | 978 ++++++++++++++++-- system/bta/le_audio/audio_set_scenarios.json | 12 + system/bta/le_audio/devices_test.cc | 7 +- 3 files changed, 916 insertions(+), 81 deletions(-) diff --git a/system/bta/le_audio/audio_set_configurations.json b/system/bta/le_audio/audio_set_configurations.json index c3b9200ee2d..4adda2edd49 100644 --- a/system/bta/le_audio/audio_set_configurations.json +++ b/system/bta/le_audio/audio_set_configurations.json @@ -132,6 +132,26 @@ "codec_config_name": "SingleDev_TwoChanStereoSnk_16_2", "qos_config_name": ["QoS_Config_16_2_2"] }, + { + "name": "SingleDev_OneChanMonoSnk_32_1_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSnk_32_1", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSnk_32_1_1", + "codec_config_name": "SingleDev_OneChanMonoSnk_32_1", + "qos_config_name": ["QoS_Config_32_1_1"] + }, + { + "name": "SingleDev_OneChanMonoSnk_32_2_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSnk_32_2", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSnk_32_2_1", + "codec_config_name": "SingleDev_OneChanMonoSnk_32_2", + "qos_config_name": ["QoS_Config_32_2_1"] + }, { "name": "SingleDev_OneChanMonoSnk_16_1_Server_Preferred", "codec_config_name": "SingleDev_OneChanMonoSnk_16_1", @@ -372,16 +392,56 @@ "codec_config_name": "SingleDev_OneChanStereoSrc_16_2", "qos_config_name": ["QoS_Config_Server_Preferred"] }, + { + "name": "SingleDev_OneChanMonoSrc_48_4_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_48_4", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSrc_48_3_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_48_3", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSrc_48_2_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_48_2", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSrc_48_1_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_48_1", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSrc_32_2_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_32_2", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, + { + "name": "SingleDev_OneChanMonoSrc_32_1_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_32_1", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, { "name": "SingleDev_OneChanMonoSrc_24_2_Server_Preferred", "codec_config_name": "SingleDev_OneChanMonoSrc_24_2", "qos_config_name": ["QoS_Config_Server_Preferred"] }, + { + "name": "SingleDev_OneChanMonoSrc_24_1_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_24_1", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, { "name": "SingleDev_OneChanMonoSrc_16_2_Server_Preferred", "codec_config_name": "SingleDev_OneChanMonoSrc_16_2", "qos_config_name": ["QoS_Config_Server_Preferred"] }, + { + "name": "SingleDev_OneChanMonoSrc_16_1_Server_Preferred", + "codec_config_name": "SingleDev_OneChanMonoSrc_16_1", + "qos_config_name": ["QoS_Config_Server_Preferred"] + }, { "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_2", "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1", @@ -1499,6 +1559,140 @@ } ] }, + { + "name": "SingleDev_OneChanMonoSnk_32_2", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SINK", + "configuration_strategy": "MONO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 6 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 80, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "SingleDev_OneChanMonoSnk_32_1", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SINK", + "configuration_strategy": "MONO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 6 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 0 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 60, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, { "name": "SingleDev_OneChanMonoSnk_16_2", "subconfigurations": [ @@ -2169,7 +2363,521 @@ "type": 2, "compound_value": { "value": [ - 1 + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 40, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + }, + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SOURCE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 40, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1", + "subconfigurations": [ + { + "device_cnt": 2, + "ase_cnt": 4, + "direction": "SINK", + "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 0 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 30, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + }, + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SOURCE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 0 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 30, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32_2", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SINK", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 6 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 3, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 80, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + }, + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SOURCE", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 6 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 3, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 80, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SINK", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 3, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 40, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + }, + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SOURCE", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 3, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 40, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SINK", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 3 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 0 ] } }, @@ -2178,7 +2886,7 @@ "type": 3, "compound_value": { "value": [ - 1, + 3, 0, 0, 0 @@ -2190,7 +2898,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 30, 0 ] } @@ -2210,6 +2918,7 @@ "device_cnt": 1, "ase_cnt": 1, "direction": "SOURCE", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2230,7 +2939,7 @@ "type": 2, "compound_value": { "value": [ - 1 + 0 ] } }, @@ -2239,7 +2948,7 @@ "type": 3, "compound_value": { "value": [ - 1, + 3, 0, 0, 0 @@ -2251,7 +2960,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 30, 0 ] } @@ -2270,13 +2979,13 @@ ] }, { - "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1", + "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2", "subconfigurations": [ { - "device_cnt": 2, - "ase_cnt": 4, + "device_cnt": 1, + "ase_cnt": 1, "direction": "SINK", - "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", + "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2297,7 +3006,7 @@ "type": 2, "compound_value": { "value": [ - 0 + 1 ] } }, @@ -2306,7 +3015,7 @@ "type": 3, "compound_value": { "value": [ - 1, + 3, 0, 0, 0 @@ -2318,7 +3027,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 40, 0 ] } @@ -2358,7 +3067,7 @@ "type": 2, "compound_value": { "value": [ - 0 + 1 ] } }, @@ -2379,7 +3088,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 40, 0 ] } @@ -2398,7 +3107,7 @@ ] }, { - "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32_2", + "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1", "subconfigurations": [ { "device_cnt": 1, @@ -2416,7 +3125,7 @@ "type": 1, "compound_value": { "value": [ - 6 + 3 ] } }, @@ -2425,7 +3134,7 @@ "type": 2, "compound_value": { "value": [ - 1 + 0 ] } }, @@ -2446,7 +3155,7 @@ "type": 4, "compound_value": { "value": [ - 80, + 30, 0 ] } @@ -2466,7 +3175,6 @@ "device_cnt": 1, "ase_cnt": 1, "direction": "SOURCE", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2478,7 +3186,7 @@ "type": 1, "compound_value": { "value": [ - 6 + 3 ] } }, @@ -2487,7 +3195,7 @@ "type": 2, "compound_value": { "value": [ - 1 + 0 ] } }, @@ -2496,7 +3204,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2508,7 +3216,7 @@ "type": 4, "compound_value": { "value": [ - 80, + 30, 0 ] } @@ -2527,13 +3235,13 @@ ] }, { - "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2", + "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2", "subconfigurations": [ { "device_cnt": 1, - "ase_cnt": 1, + "ase_cnt": 2, "direction": "SINK", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2563,7 +3271,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2595,7 +3303,6 @@ "device_cnt": 1, "ase_cnt": 1, "direction": "SOURCE", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2625,7 +3332,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2656,13 +3363,13 @@ ] }, { - "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1", + "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1", "subconfigurations": [ { "device_cnt": 1, - "ase_cnt": 1, + "ase_cnt": 2, "direction": "SINK", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", + "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2692,7 +3399,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2724,7 +3431,6 @@ "device_cnt": 1, "ase_cnt": 1, "direction": "SOURCE", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2754,7 +3460,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2785,13 +3491,12 @@ ] }, { - "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2", + "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2", "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, "direction": "SINK", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2821,7 +3526,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -2913,13 +3618,12 @@ ] }, { - "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1", + "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1", "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, "direction": "SINK", - "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -2949,7 +3653,7 @@ "type": 3, "compound_value": { "value": [ - 3, + 1, 0, 0, 0 @@ -3041,13 +3745,12 @@ ] }, { - "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2", + "name": "DualDev_OneChanMonoSrc_16_2", "subconfigurations": [ { - "device_cnt": 1, + "device_cnt": 2, "ase_cnt": 2, - "direction": "SINK", - "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", + "direction": "SOURCE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -3104,7 +3807,12 @@ } } ] - }, + } + ] + }, + { + "name": "SingleDev_OneChanMonoSrc_48_4", + "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, @@ -3120,7 +3828,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 8 ] } }, @@ -3150,7 +3858,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 120, 0 ] } @@ -3169,13 +3877,12 @@ ] }, { - "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1", + "name": "SingleDev_OneChanMonoSrc_48_3", "subconfigurations": [ { "device_cnt": 1, - "ase_cnt": 2, - "direction": "SINK", - "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE", + "ase_cnt": 1, + "direction": "SOURCE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -3187,7 +3894,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 8 ] } }, @@ -3217,7 +3924,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 90, 0 ] } @@ -3232,7 +3939,12 @@ } } ] - }, + } + ] + }, + { + "name": "SingleDev_OneChanMonoSrc_48_2", + "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, @@ -3248,7 +3960,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 8 ] } }, @@ -3257,7 +3969,7 @@ "type": 2, "compound_value": { "value": [ - 0 + 1 ] } }, @@ -3278,7 +3990,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 100, 0 ] } @@ -3297,12 +4009,12 @@ ] }, { - "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2", + "name": "SingleDev_OneChanMonoSrc_48_1", "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, - "direction": "SINK", + "direction": "SOURCE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -3314,7 +4026,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 8 ] } }, @@ -3323,7 +4035,7 @@ "type": 2, "compound_value": { "value": [ - 1 + 0 ] } }, @@ -3344,7 +4056,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 75, 0 ] } @@ -3359,7 +4071,12 @@ } } ] - }, + } + ] + }, + { + "name": "SingleDev_OneChanMonoSrc_32_2", + "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, @@ -3375,7 +4092,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 6 ] } }, @@ -3405,7 +4122,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 80, 0 ] } @@ -3424,12 +4141,12 @@ ] }, { - "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1", + "name": "SingleDev_OneChanMonoSrc_32_1", "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, - "direction": "SINK", + "direction": "SOURCE", "codec_id": { "coding_format": 6, "vendor_company_id": 0, @@ -3441,7 +4158,7 @@ "type": 1, "compound_value": { "value": [ - 3 + 6 ] } }, @@ -3471,7 +4188,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 60, 0 ] } @@ -3486,7 +4203,12 @@ } } ] - }, + } + ] + }, + { + "name": "SingleDev_OneChanMonoSrc_24_2", + "subconfigurations": [ { "device_cnt": 1, "ase_cnt": 1, @@ -3502,7 +4224,73 @@ "type": 1, "compound_value": { "value": [ - 3 + 5 + ] + } + }, + { + "name": "frame_duration", + "type": 2, + "compound_value": { + "value": [ + 1 + ] + } + }, + { + "name": "audio_channel_allocation", + "type": 3, + "compound_value": { + "value": [ + 1, + 0, + 0, + 0 + ] + } + }, + { + "name": "octets_per_codec_frame", + "type": 4, + "compound_value": { + "value": [ + 60, + 0 + ] + } + }, + { + "name": "codec_frame_blocks_per_sdu", + "type": 5, + "compound_value": { + "value": [ + 1 + ] + } + } + ] + } + ] + }, + { + "name": "SingleDev_OneChanMonoSrc_24_1", + "subconfigurations": [ + { + "device_cnt": 1, + "ase_cnt": 1, + "direction": "SOURCE", + "codec_id": { + "coding_format": 6, + "vendor_company_id": 0, + "vendor_codec_id": 0 + }, + "codec_configuration": [ + { + "name": "sampling_frequency", + "type": 1, + "compound_value": { + "value": [ + 5 ] } }, @@ -3532,7 +4320,7 @@ "type": 4, "compound_value": { "value": [ - 30, + 45, 0 ] } @@ -3551,11 +4339,11 @@ ] }, { - "name": "DualDev_OneChanMonoSrc_16_2", + "name": "SingleDev_OneChanMonoSrc_16_2", "subconfigurations": [ { - "device_cnt": 2, - "ase_cnt": 2, + "device_cnt": 1, + "ase_cnt": 1, "direction": "SOURCE", "codec_id": { "coding_format": 6, @@ -3617,7 +4405,7 @@ ] }, { - "name": "SingleDev_OneChanMonoSrc_16_2", + "name": "SingleDev_OneChanMonoSrc_16_1", "subconfigurations": [ { "device_cnt": 1, @@ -3643,7 +4431,7 @@ "type": 2, "compound_value": { "value": [ - 1 + 0 ] } }, @@ -3664,7 +4452,7 @@ "type": 4, "compound_value": { "value": [ - 40, + 30, 0 ] } @@ -9083,16 +9871,46 @@ "retransmission_number": 13, "max_transport_latency": 95 }, + { + "name": "QoS_Config_24_1_1", + "retransmission_number": 2, + "max_transport_latency": 8 + }, + { + "name": "QoS_Config_24_1_2", + "retransmission_number": 13, + "max_transport_latency": 75 + }, + { + "name": "QoS_Config_24_2_1", + "retransmission_number": 2, + "max_transport_latency": 10 + }, { "name": "QoS_Config_24_2_2", "retransmission_number": 13, "max_transport_latency": 95 }, + { + "name": "QoS_Config_32_1_1", + "retransmission_number": 2, + "max_transport_latency": 8 + }, + { + "name": "QoS_Config_32_1_2", + "retransmission_number": 13, + "max_transport_latency": 75 + }, { "name": "QoS_Config_32_2_1", "retransmission_number": 2, "max_transport_latency": 10 }, + { + "name": "QoS_Config_32_2_2", + "retransmission_number": 13, + "max_transport_latency": 95 + }, { "name": "QoS_Config_48_1_2", "retransmission_number": 13, diff --git a/system/bta/le_audio/audio_set_scenarios.json b/system/bta/le_audio/audio_set_scenarios.json index 4013e7e5295..58ad4f532b4 100644 --- a/system/bta/le_audio/audio_set_scenarios.json +++ b/system/bta/le_audio/audio_set_scenarios.json @@ -91,8 +91,16 @@ "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_1", "DualDev_OneChanMonoSrc_16_2_Server_Preferred", "SingleDev_OneChanStereoSrc_16_2_Server_Preferred", + "SingleDev_OneChanMonoSrc_48_4_Server_Preferred", + "SingleDev_OneChanMonoSrc_48_3_Server_Preferred", + "SingleDev_OneChanMonoSrc_48_2_Server_Preferred", + "SingleDev_OneChanMonoSrc_48_1_Server_Preferred", + "SingleDev_OneChanMonoSrc_32_2_Server_Preferred", + "SingleDev_OneChanMonoSrc_32_1_Server_Preferred", "SingleDev_OneChanMonoSrc_24_2_Server_Preferred", + "SingleDev_OneChanMonoSrc_24_1_Server_Preferred", "SingleDev_OneChanMonoSrc_16_2_Server_Preferred", + "SingleDev_OneChanMonoSrc_16_1_Server_Preferred", "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_Server_Prefered_1", "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_60oct_R3_L22_1" ] @@ -152,6 +160,10 @@ "SingleDev_OneChanMonoSnk_48_2_2", "SingleDev_OneChanMonoSnk_48_1_Server_Preferred", "SingleDev_OneChanMonoSnk_48_1_2", + "SingleDev_OneChanMonoSnk_32_2_Server_Preferred", + "SingleDev_OneChanMonoSnk_32_2_2", + "SingleDev_OneChanMonoSnk_32_1_Server_Preferred", + "SingleDev_OneChanMonoSnk_32_1_2", "SingleDev_OneChanMonoSnk_24_2_Server_Preferred", "SingleDev_OneChanMonoSnk_24_2_2", "SingleDev_OneChanMonoSnk_16_2_Server_Preferred", diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc index 2cb62d6afd2..6b48fdca613 100644 --- a/system/bta/le_audio/devices_test.cc +++ b/system/bta/le_audio/devices_test.cc @@ -207,13 +207,18 @@ bool IsLc3SettingSupported(LeAudioContextType context_type, Lc3SettingId id) { case LeAudioContextType::CONVERSATIONAL: if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 || - id == Lc3SettingId::LC3_24_2 || id == Lc3SettingId::LC3_32_2) + id == Lc3SettingId::LC3_24_1 || id == Lc3SettingId::LC3_24_2 || + id == Lc3SettingId::LC3_32_1 || id == Lc3SettingId::LC3_32_2 || + id == Lc3SettingId::LC3_48_1 || id == Lc3SettingId::LC3_48_2 || + id == Lc3SettingId::LC3_48_3 || id == Lc3SettingId::LC3_48_4 || + id == Lc3SettingId::LC3_VND_1) return true; break; case LeAudioContextType::MEDIA: if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 || + id == Lc3SettingId::LC3_32_1 || id == Lc3SettingId::LC3_32_2 || id == Lc3SettingId::LC3_48_4 || id == Lc3SettingId::LC3_48_2 || id == Lc3SettingId::LC3_VND_1 || id == Lc3SettingId::LC3_24_2) return true; -- GitLab From acadf166a3c9707083f7abf4350af4eba649da55 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Tue, 9 Aug 2022 23:15:58 +0000 Subject: [PATCH 070/998] HeadsetPhoneState: Synchronize constructor with phone state listening functions. Bug: 241871296 The bug is that mSignalStrengthUpdateRequest is null when bt calls mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest). mSignalStrengthUpdateRequest is created at constructor. So the only explanation is that there is a race condition between the constructor and stopListenForPhoneState() who calls clearSignalStrengthUpdateRequest(); Test: atest BluetoothInstrumentationTests Change-Id: I0d456a4a7981e1befe291d2045b926a4da4b6bf5 Merged-In: I0d456a4a7981e1befe291d2045b926a4da4b6bf5 (cherry picked from a8e1aab54a18296bcb33eef34e4eeb7505f2e406) --- .../bluetooth/hfp/HeadsetPhoneState.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java index 09af6886ba3..440cf815748 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(); + } } /** -- GitLab From 3baf1d6fa2b80197fcc278d1d29c6074065e2442 Mon Sep 17 00:00:00 2001 From: Rahul Sabnis Date: Thu, 11 Aug 2022 20:46:25 +0000 Subject: [PATCH 071/998] Ignore calls to set the address policy after it has already been set Bug: 239792403 Bug: 237572866 Bug: 195410559 Change-Id: I92e7d482da8aa3afd9b7610fc0cf92f2c5357270 Test: system/gd/cert/run --clean --sl4a OobPairingSl4aTest Test: system/gd/cert/run --clean --sl4a_sl4a OobPairingTest Tag: #refactor Ignore-AOSP-First: Security fix --- system/gd/hci/le_address_manager.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/system/gd/hci/le_address_manager.cc b/system/gd/hci/le_address_manager.cc index 3271d16a396..9634a5eee1e 100644 --- a/system/gd/hci/le_address_manager.cc +++ b/system/gd/hci/le_address_manager.cc @@ -51,14 +51,17 @@ void LeAddressManager::SetPrivacyPolicyForInitiatorAddress( bool supports_ble_privacy, std::chrono::milliseconds minimum_rotation_time, std::chrono::milliseconds maximum_rotation_time) { - // Need to update some parameteres like IRK - if (supports_ble_privacy && address_policy_ != AddressPolicy::POLICY_NOT_SET) { + // Handle repeated calls to the function + if (address_policy_ != AddressPolicy::POLICY_NOT_SET) { + // Need to update some parameteres like IRK if privacy is supported + if (supports_ble_privacy) { LOG_INFO("Updating rotation parameters."); rotation_irk_ = rotation_irk; minimum_rotation_time_ = minimum_rotation_time; maximum_rotation_time_ = maximum_rotation_time; set_random_address(); - return; + } + return; } ASSERT(address_policy_ == AddressPolicy::POLICY_NOT_SET); ASSERT(address_policy != AddressPolicy::POLICY_NOT_SET); -- GitLab From dc17e836d01007435546f69bcac54ec2313a5b60 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 11 Aug 2022 14:49:42 -0700 Subject: [PATCH 072/998] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ic4f8f4c7f4fcce94b09c985b2d00414bcf649f9d --- android/app/res/values-or/strings_pbap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/res/values-or/strings_pbap.xml b/android/app/res/values-or/strings_pbap.xml index 40c1e7f4d25..914f58745d9 100644 --- a/android/app/res/values-or/strings_pbap.xml +++ b/android/app/res/values-or/strings_pbap.xml @@ -12,5 +12,5 @@ "ଅଜଣା ନାମ" "ମୋର ନାମ" "000000" - "ବ୍ଲୁଟୂଥ୍‍‌ ଯୋଗାଯୋଗ ସେୟାର୍‌ କରନ୍ତୁ" + "ବ୍ଲୁଟୂଥ କଣ୍ଟାକ୍ଟ ସେୟାର କରନ୍ତୁ" -- GitLab From 3696926bd806f1e9622095bd779eb892a5ce67ad Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 11 Aug 2022 14:50:15 -0700 Subject: [PATCH 073/998] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I8e940f0ae1d98eee82dea4a971572d60bd288c2f --- android/app/res/values-or/strings_pbap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/res/values-or/strings_pbap.xml b/android/app/res/values-or/strings_pbap.xml index 40c1e7f4d25..914f58745d9 100644 --- a/android/app/res/values-or/strings_pbap.xml +++ b/android/app/res/values-or/strings_pbap.xml @@ -12,5 +12,5 @@ "ଅଜଣା ନାମ" "ମୋର ନାମ" "000000" - "ବ୍ଲୁଟୂଥ୍‍‌ ଯୋଗାଯୋଗ ସେୟାର୍‌ କରନ୍ତୁ" + "ବ୍ଲୁଟୂଥ କଣ୍ଟାକ୍ଟ ସେୟାର କରନ୍ତୁ" -- GitLab From 9823625ebb61f66c8daf2b9d28507370038a3b41 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 11 Aug 2022 14:50:46 -0700 Subject: [PATCH 074/998] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I92ef07a548998e4e2a713a7c1f830ba491795d71 --- android/app/res/values-or/strings_pbap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/res/values-or/strings_pbap.xml b/android/app/res/values-or/strings_pbap.xml index 40c1e7f4d25..914f58745d9 100644 --- a/android/app/res/values-or/strings_pbap.xml +++ b/android/app/res/values-or/strings_pbap.xml @@ -12,5 +12,5 @@ "ଅଜଣା ନାମ" "ମୋର ନାମ" "000000" - "ବ୍ଲୁଟୂଥ୍‍‌ ଯୋଗାଯୋଗ ସେୟାର୍‌ କରନ୍ତୁ" + "ବ୍ଲୁଟୂଥ କଣ୍ଟାକ୍ଟ ସେୟାର କରନ୍ତୁ" -- GitLab From c0ddd108532f144a376a421ee7591aa99f5b4450 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 11 Aug 2022 14:53:29 -0700 Subject: [PATCH 075/998] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I6bc1e210710d3a26a383a3d0ac3625ff782e487b --- android/app/res/values-af/strings.xml | 10 ++++++++++ android/app/res/values-am/strings.xml | 10 ++++++++++ android/app/res/values-ar/strings.xml | 10 ++++++++++ android/app/res/values-as/strings.xml | 10 ++++++++++ android/app/res/values-az/strings.xml | 10 ++++++++++ android/app/res/values-b+sr+Latn/strings.xml | 10 ++++++++++ android/app/res/values-be/strings.xml | 10 ++++++++++ android/app/res/values-bg/strings.xml | 10 ++++++++++ android/app/res/values-bn/strings.xml | 10 ++++++++++ android/app/res/values-bs/strings.xml | 5 +++++ android/app/res/values-ca/strings.xml | 10 ++++++++++ android/app/res/values-cs/strings.xml | 10 ++++++++++ android/app/res/values-da/strings.xml | 10 ++++++++++ android/app/res/values-de/strings.xml | 10 ++++++++++ android/app/res/values-el/strings.xml | 10 ++++++++++ android/app/res/values-en-rAU/strings.xml | 5 +++++ android/app/res/values-en-rCA/strings.xml | 5 +++++ android/app/res/values-en-rGB/strings.xml | 5 +++++ android/app/res/values-en-rIN/strings.xml | 5 +++++ android/app/res/values-en-rXC/strings.xml | 5 +++++ android/app/res/values-es-rUS/strings.xml | 10 ++++++++++ android/app/res/values-es/strings.xml | 10 ++++++++++ android/app/res/values-et/strings.xml | 10 ++++++++++ android/app/res/values-eu/strings.xml | 10 ++++++++++ android/app/res/values-fa/strings.xml | 10 ++++++++++ android/app/res/values-fi/strings.xml | 10 ++++++++++ android/app/res/values-fr-rCA/strings.xml | 10 ++++++++++ android/app/res/values-fr/strings.xml | 10 ++++++++++ android/app/res/values-gl/strings.xml | 10 ++++++++++ android/app/res/values-gu/strings.xml | 10 ++++++++++ android/app/res/values-hi/strings.xml | 10 ++++++++++ android/app/res/values-hr/strings.xml | 5 +++++ android/app/res/values-hu/strings.xml | 10 ++++++++++ android/app/res/values-hy/strings.xml | 10 ++++++++++ android/app/res/values-in/strings.xml | 10 ++++++++++ android/app/res/values-is/strings.xml | 10 ++++++++++ android/app/res/values-it/strings.xml | 10 ++++++++++ android/app/res/values-iw/strings.xml | 10 ++++++++++ android/app/res/values-ja/strings.xml | 10 ++++++++++ android/app/res/values-ka/strings.xml | 10 ++++++++++ android/app/res/values-kk/strings.xml | 10 ++++++++++ android/app/res/values-km/strings.xml | 10 ++++++++++ android/app/res/values-kn/strings.xml | 10 ++++++++++ android/app/res/values-ko/strings.xml | 10 ++++++++++ android/app/res/values-ky/strings.xml | 10 ++++++++++ android/app/res/values-lo/strings.xml | 10 ++++++++++ android/app/res/values-lt/strings.xml | 10 ++++++++++ android/app/res/values-lv/strings.xml | 10 ++++++++++ android/app/res/values-mk/strings.xml | 10 ++++++++++ android/app/res/values-ml/strings.xml | 10 ++++++++++ android/app/res/values-mn/strings.xml | 10 ++++++++++ android/app/res/values-mr/strings.xml | 10 ++++++++++ android/app/res/values-ms/strings.xml | 10 ++++++++++ android/app/res/values-my/strings.xml | 10 ++++++++++ android/app/res/values-nb/strings.xml | 10 ++++++++++ android/app/res/values-ne/strings.xml | 10 ++++++++++ android/app/res/values-nl/strings.xml | 10 ++++++++++ android/app/res/values-or/strings.xml | 10 ++++++++++ android/app/res/values-pa/strings.xml | 10 ++++++++++ android/app/res/values-pl/strings.xml | 10 ++++++++++ android/app/res/values-pt-rPT/strings.xml | 10 ++++++++++ android/app/res/values-pt/strings.xml | 10 ++++++++++ android/app/res/values-ro/strings.xml | 10 ++++++++++ android/app/res/values-ru/strings.xml | 10 ++++++++++ android/app/res/values-si/strings.xml | 10 ++++++++++ android/app/res/values-sk/strings.xml | 10 ++++++++++ android/app/res/values-sl/strings.xml | 10 ++++++++++ android/app/res/values-sq/strings.xml | 10 ++++++++++ android/app/res/values-sr/strings.xml | 10 ++++++++++ android/app/res/values-sv/strings.xml | 10 ++++++++++ android/app/res/values-sw/strings.xml | 10 ++++++++++ android/app/res/values-ta/strings.xml | 10 ++++++++++ android/app/res/values-te/strings.xml | 10 ++++++++++ android/app/res/values-th/strings.xml | 10 ++++++++++ android/app/res/values-tl/strings.xml | 10 ++++++++++ android/app/res/values-tr/strings.xml | 10 ++++++++++ android/app/res/values-uk/strings.xml | 10 ++++++++++ android/app/res/values-ur/strings.xml | 10 ++++++++++ android/app/res/values-uz/strings.xml | 10 ++++++++++ android/app/res/values-vi/strings.xml | 10 ++++++++++ android/app/res/values-zh-rCN/strings.xml | 10 ++++++++++ android/app/res/values-zh-rHK/strings.xml | 10 ++++++++++ android/app/res/values-zh-rTW/strings.xml | 10 ++++++++++ android/app/res/values-zu/strings.xml | 10 ++++++++++ 84 files changed, 805 insertions(+) diff --git a/android/app/res/values-af/strings.xml b/android/app/res/values-af/strings.xml index 6e5004c4b52..257b09ad347 100644 --- a/android/app/res/values-af/strings.xml +++ b/android/app/res/values-af/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-oudio" "Lêers groter as 4 GB kan nie oorgedra word nie" "Koppel aan Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-am/strings.xml b/android/app/res/values-am/strings.xml index 9e534f28b48..7e75ba6daec 100644 --- a/android/app/res/values-am/strings.xml +++ b/android/app/res/values-am/strings.xml @@ -128,4 +128,14 @@ "የብሉቱዝ ኦዲዮ" "ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም" "ከብሉቱዝ ጋር ተገናኝ" + + + + + + + + + + diff --git a/android/app/res/values-ar/strings.xml b/android/app/res/values-ar/strings.xml index 87b7ca6abca..b0bf136def1 100644 --- a/android/app/res/values-ar/strings.xml +++ b/android/app/res/values-ar/strings.xml @@ -128,4 +128,14 @@ "بث صوتي عبر البلوتوث" "يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت" "الاتصال ببلوتوث" + + + + + + + + + + diff --git a/android/app/res/values-as/strings.xml b/android/app/res/values-as/strings.xml index 4ecc5fbee33..e0305997905 100644 --- a/android/app/res/values-as/strings.xml +++ b/android/app/res/values-as/strings.xml @@ -128,4 +128,14 @@ "ব্লুটুথ অডিঅ\'" "৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি" "ব্লুটুথৰ সৈতে সংযোগ কৰক" + + + + + + + + + + diff --git a/android/app/res/values-az/strings.xml b/android/app/res/values-az/strings.xml index 955699203d8..81451b6a120 100644 --- a/android/app/res/values-az/strings.xml +++ b/android/app/res/values-az/strings.xml @@ -128,4 +128,14 @@ "Bluetooth audio" "4GB-dən böyük olan faylları köçürmək mümkün deyil" "Bluetooth\'a qoşulun" + + + + + + + + + + diff --git a/android/app/res/values-b+sr+Latn/strings.xml b/android/app/res/values-b+sr+Latn/strings.xml index 92f842310f4..7647c814806 100644 --- a/android/app/res/values-b+sr+Latn/strings.xml +++ b/android/app/res/values-b+sr+Latn/strings.xml @@ -128,4 +128,14 @@ "Bluetooth audio" "Ne mogu da se prenose datoteke veće od 4 GB" "Poveži sa Bluetooth-om" + + + + + + + + + + diff --git a/android/app/res/values-be/strings.xml b/android/app/res/values-be/strings.xml index ed72ab67c93..87b33a023ff 100644 --- a/android/app/res/values-be/strings.xml +++ b/android/app/res/values-be/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-аўдыя" "Немагчыма перадаць файлы, большыя за 4 ГБ" "Падключыцца да Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-bg/strings.xml b/android/app/res/values-bg/strings.xml index b2d67422ab9..cc38f013a6c 100644 --- a/android/app/res/values-bg/strings.xml +++ b/android/app/res/values-bg/strings.xml @@ -128,4 +128,14 @@ "Аудио през Bluetooth" "Файловете с размер над 4 ГБ не могат да бъдат прехвърлени" "Свързване с Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-bn/strings.xml b/android/app/res/values-bn/strings.xml index c85dbf37743..cf19a68f38c 100644 --- a/android/app/res/values-bn/strings.xml +++ b/android/app/res/values-bn/strings.xml @@ -128,4 +128,14 @@ "ব্লুটুথ অডিও" "৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না" "ব্লুটুথের সাথে কানেক্ট করুন" + + + + + + + + + + diff --git a/android/app/res/values-bs/strings.xml b/android/app/res/values-bs/strings.xml index c4fadb0db31..2ccba01ca04 100644 --- a/android/app/res/values-bs/strings.xml +++ b/android/app/res/values-bs/strings.xml @@ -128,4 +128,9 @@ "Bluetooth Audio" "Nije moguće prenijeti fajlove veće od 4 GB" "Poveži se na Bluetooth" + "Uključili ste Bluetooth" + "Vaš telefon zadržat će Bluetooth u načinu rada u zrakoplovu, osim ako ga isključite kad ste u tom načinu rada" + "Bluetooth ostaje uključen" + "Bluetooth i Wi-Fi ostaju uključeni" + "Vaš telefon zadržat će oboje u načinu rada u zrakoplovu, osim ako ga isključite kad ste u tom načinu rada" diff --git a/android/app/res/values-ca/strings.xml b/android/app/res/values-ca/strings.xml index b5ec8423086..b6dd2433252 100644 --- a/android/app/res/values-ca/strings.xml +++ b/android/app/res/values-ca/strings.xml @@ -128,4 +128,14 @@ "Àudio per Bluetooth" "No es poden transferir fitxers més grans de 4 GB" "Connecta el Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-cs/strings.xml b/android/app/res/values-cs/strings.xml index 5352a25bece..186d3ddd65d 100644 --- a/android/app/res/values-cs/strings.xml +++ b/android/app/res/values-cs/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "Soubory větší než 4 GB nelze přenést" "Připojit k Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-da/strings.xml b/android/app/res/values-da/strings.xml index 15881ed4f18..a6437c72d28 100644 --- a/android/app/res/values-da/strings.xml +++ b/android/app/res/values-da/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-lyd" "File, der er større end 4 GB, kan ikke overføres" "Opret forbindelse til Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-de/strings.xml b/android/app/res/values-de/strings.xml index 719dd9532b0..b8ad5b9a38c 100644 --- a/android/app/res/values-de/strings.xml +++ b/android/app/res/values-de/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-Audio" "Dateien mit mehr als 4 GB können nicht übertragen werden" "Mit Bluetooth verbinden" + + + + + + + + + + diff --git a/android/app/res/values-el/strings.xml b/android/app/res/values-el/strings.xml index 1b6ef5852e5..4ce893ec268 100644 --- a/android/app/res/values-el/strings.xml +++ b/android/app/res/values-el/strings.xml @@ -128,4 +128,14 @@ "Ήχος Bluetooth" "Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB" "Σύνδεση σε Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-en-rAU/strings.xml b/android/app/res/values-en-rAU/strings.xml index 266667fd536..54c76ccc15a 100644 --- a/android/app/res/values-en-rAU/strings.xml +++ b/android/app/res/values-en-rAU/strings.xml @@ -128,4 +128,9 @@ "Bluetooth audio" "Files bigger than 4 GB cannot be transferred" "Connect to Bluetooth" + "You turned on Bluetooth" + "Your phone will keep Bluetooth on in aeroplane mode, unless you turn it off while in this mode" + "Bluetooth stays on" + "Bluetooth and Wi-Fi stays on" + "Your phone will keep both on in aeroplane mode, unless you turn it off while in this mode" diff --git a/android/app/res/values-en-rCA/strings.xml b/android/app/res/values-en-rCA/strings.xml index 89e7e55847b..4e4a2fef16b 100644 --- a/android/app/res/values-en-rCA/strings.xml +++ b/android/app/res/values-en-rCA/strings.xml @@ -128,4 +128,9 @@ "Bluetooth audio" "Files bigger than 4 GB cannot be transferred" "Connect to Bluetooth" + "You turned on Bluetooth" + "Your phone will keep Bluetooth on in aeroplane mode, unless you turn it off while in this mode" + "Bluetooth stays on" + "Bluetooth and Wi-Fi stays on" + "Your phone will keep both on in aeroplane mode, unless you turn it off while in this mode" diff --git a/android/app/res/values-en-rGB/strings.xml b/android/app/res/values-en-rGB/strings.xml index 266667fd536..54c76ccc15a 100644 --- a/android/app/res/values-en-rGB/strings.xml +++ b/android/app/res/values-en-rGB/strings.xml @@ -128,4 +128,9 @@ "Bluetooth audio" "Files bigger than 4 GB cannot be transferred" "Connect to Bluetooth" + "You turned on Bluetooth" + "Your phone will keep Bluetooth on in aeroplane mode, unless you turn it off while in this mode" + "Bluetooth stays on" + "Bluetooth and Wi-Fi stays on" + "Your phone will keep both on in aeroplane mode, unless you turn it off while in this mode" diff --git a/android/app/res/values-en-rIN/strings.xml b/android/app/res/values-en-rIN/strings.xml index 266667fd536..54c76ccc15a 100644 --- a/android/app/res/values-en-rIN/strings.xml +++ b/android/app/res/values-en-rIN/strings.xml @@ -128,4 +128,9 @@ "Bluetooth audio" "Files bigger than 4 GB cannot be transferred" "Connect to Bluetooth" + "You turned on Bluetooth" + "Your phone will keep Bluetooth on in aeroplane mode, unless you turn it off while in this mode" + "Bluetooth stays on" + "Bluetooth and Wi-Fi stays on" + "Your phone will keep both on in aeroplane mode, unless you turn it off while in this mode" diff --git a/android/app/res/values-en-rXC/strings.xml b/android/app/res/values-en-rXC/strings.xml index a47fdcdbc38..558646837d4 100644 --- a/android/app/res/values-en-rXC/strings.xml +++ b/android/app/res/values-en-rXC/strings.xml @@ -128,4 +128,9 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎Bluetooth Audio‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎Files bigger than 4GB cannot be transferred‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎Connect to Bluetooth‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎You turned on Bluetooth‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎Your phone will keep Bluetooth on in airplane mode, unless you turn it off while in this mode‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‎‎Bluetooth stays on‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎Bluetooth and Wi-Fi stays on‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎Your phone will keep both on in airplane mode, unless you turn it off while in this mode‎‏‎‎‏‎" diff --git a/android/app/res/values-es-rUS/strings.xml b/android/app/res/values-es-rUS/strings.xml index d71996e76a6..7a54c07666b 100644 --- a/android/app/res/values-es-rUS/strings.xml +++ b/android/app/res/values-es-rUS/strings.xml @@ -128,4 +128,14 @@ "Audio Bluetooth" "No se pueden transferir los archivos de más de 4 GB" "Conectarse a Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-es/strings.xml b/android/app/res/values-es/strings.xml index dcfe458023c..86c1eeb31aa 100644 --- a/android/app/res/values-es/strings.xml +++ b/android/app/res/values-es/strings.xml @@ -128,4 +128,14 @@ "Audio por Bluetooth" "No se pueden transferir archivos de más de 4 GB" "Conectarse a un dispositivo Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-et/strings.xml b/android/app/res/values-et/strings.xml index f4c8c87d628..a140b0e7a7b 100644 --- a/android/app/res/values-et/strings.xml +++ b/android/app/res/values-et/strings.xml @@ -128,4 +128,14 @@ "Bluetoothi heli" "Faile, mis on üle 4 GB, ei saa üle kanda" "Ühenda Bluetoothiga" + + + + + + + + + + diff --git a/android/app/res/values-eu/strings.xml b/android/app/res/values-eu/strings.xml index d48e5a06f35..391b62cc4b7 100644 --- a/android/app/res/values-eu/strings.xml +++ b/android/app/res/values-eu/strings.xml @@ -128,4 +128,14 @@ "Bluetooth bidezko audioa" "Ezin dira transferitu 4 GB baino gehiagoko fitxategiak" "Konektatu Bluetooth-era" + + + + + + + + + + diff --git a/android/app/res/values-fa/strings.xml b/android/app/res/values-fa/strings.xml index 7fe21bde4ff..39e8be849c0 100644 --- a/android/app/res/values-fa/strings.xml +++ b/android/app/res/values-fa/strings.xml @@ -128,4 +128,14 @@ "بلوتوث‌ صوتی" "فایل‌های بزرگ‌تر از ۴ گیگابایت نمی‌توانند منتقل شوند" "اتصال به بلوتوث" + + + + + + + + + + diff --git a/android/app/res/values-fi/strings.xml b/android/app/res/values-fi/strings.xml index 75c005f3a4a..064c2e957de 100644 --- a/android/app/res/values-fi/strings.xml +++ b/android/app/res/values-fi/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-ääni" "Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää." "Muodosta Bluetooth-yhteys" + + + + + + + + + + diff --git a/android/app/res/values-fr-rCA/strings.xml b/android/app/res/values-fr-rCA/strings.xml index 5e10989fa30..dbe897306d9 100644 --- a/android/app/res/values-fr-rCA/strings.xml +++ b/android/app/res/values-fr-rCA/strings.xml @@ -128,4 +128,14 @@ "Audio Bluetooth" "Les fichiers dépassant 4 Go ne peuvent pas être transférés" "Connexion au Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-fr/strings.xml b/android/app/res/values-fr/strings.xml index 333b95ed8a4..5f6819124a3 100644 --- a/android/app/res/values-fr/strings.xml +++ b/android/app/res/values-fr/strings.xml @@ -128,4 +128,14 @@ "Audio Bluetooth" "Impossible de transférer les fichiers supérieurs à 4 Go" "Se connecter au Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-gl/strings.xml b/android/app/res/values-gl/strings.xml index a9b54c3fd8e..b6bc60348d9 100644 --- a/android/app/res/values-gl/strings.xml +++ b/android/app/res/values-gl/strings.xml @@ -128,4 +128,14 @@ "Audio por Bluetooth" "Non se poden transferir ficheiros de máis de 4 GB" "Conectar ao Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-gu/strings.xml b/android/app/res/values-gu/strings.xml index 3653a8aaace..75e7e8871a8 100644 --- a/android/app/res/values-gu/strings.xml +++ b/android/app/res/values-gu/strings.xml @@ -128,4 +128,14 @@ "બ્લૂટૂથ ઑડિઓ" "4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી" "બ્લૂટૂથ સાથે કનેક્ટ કરો" + + + + + + + + + + diff --git a/android/app/res/values-hi/strings.xml b/android/app/res/values-hi/strings.xml index 99dab102998..729775e1403 100644 --- a/android/app/res/values-hi/strings.xml +++ b/android/app/res/values-hi/strings.xml @@ -128,4 +128,14 @@ "ब्लूटूथ ऑडियो" "4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं" "ब्लूटूथ से कनेक्ट करें" + + + + + + + + + + diff --git a/android/app/res/values-hr/strings.xml b/android/app/res/values-hr/strings.xml index fc136d79b09..40caccbfe59 100644 --- a/android/app/res/values-hr/strings.xml +++ b/android/app/res/values-hr/strings.xml @@ -128,4 +128,9 @@ "Bluetooth Audio" "Datoteke veće od 4 GB ne mogu se prenijeti" "Povezivanje s Bluetoothom" + "Uključili ste Bluetooth" + "Vaš telefon zadržat će Bluetooth u načinu rada u zrakoplovu, osim ako ga isključite kad ste u tom načinu rada" + "Bluetooth ostaje uključen" + "Bluetooth i Wi-Fi ostaju uključeni" + "Vaš telefon zadržat će oboje u načinu rada u zrakoplovu, osim ako ga isključite kad ste u tom načinu rada" diff --git a/android/app/res/values-hu/strings.xml b/android/app/res/values-hu/strings.xml index c14d9b00809..8dd8fae1b59 100644 --- a/android/app/res/values-hu/strings.xml +++ b/android/app/res/values-hu/strings.xml @@ -128,4 +128,14 @@ "Bluetooth audió" "A 4 GB-nál nagyobb fájlokat nem lehet átvinni" "Csatlakozás Bluetooth-eszközhöz" + + + + + + + + + + diff --git a/android/app/res/values-hy/strings.xml b/android/app/res/values-hy/strings.xml index f0881ccf836..e21dbc4d882 100644 --- a/android/app/res/values-hy/strings.xml +++ b/android/app/res/values-hy/strings.xml @@ -128,4 +128,14 @@ "Bluetooth աուդիո" "4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել" "Միանալ Bluetooth-ին" + + + + + + + + + + diff --git a/android/app/res/values-in/strings.xml b/android/app/res/values-in/strings.xml index cf40fc6fef6..ca920c23ded 100644 --- a/android/app/res/values-in/strings.xml +++ b/android/app/res/values-in/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "File yang berukuran lebih dari 4GB tidak dapat ditransfer" "Hubungkan ke Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-is/strings.xml b/android/app/res/values-is/strings.xml index 5ca4de3d35b..9fb84afcd9e 100644 --- a/android/app/res/values-is/strings.xml +++ b/android/app/res/values-is/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-hljóð" "Ekki er hægt að flytja skrár sem eru stærri en 4 GB" "Tengjast við Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-it/strings.xml b/android/app/res/values-it/strings.xml index bab5e54100b..0271d3ab429 100644 --- a/android/app/res/values-it/strings.xml +++ b/android/app/res/values-it/strings.xml @@ -128,4 +128,14 @@ "Audio Bluetooth" "Impossibile trasferire file con dimensioni superiori a 4 GB" "Connettiti a Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-iw/strings.xml b/android/app/res/values-iw/strings.xml index ad24f441ba8..f085d6dc56a 100644 --- a/android/app/res/values-iw/strings.xml +++ b/android/app/res/values-iw/strings.xml @@ -128,4 +128,14 @@ "‏אודיו Bluetooth" "‏לא ניתן להעביר קבצים שגדולים מ-4GB" "‏התחברות באמצעות Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ja/strings.xml b/android/app/res/values-ja/strings.xml index 07089032caa..cde7574631d 100644 --- a/android/app/res/values-ja/strings.xml +++ b/android/app/res/values-ja/strings.xml @@ -128,4 +128,14 @@ "Bluetooth オーディオ" "4 GB を超えるファイルは転送できません" "Bluetooth に接続する" + + + + + + + + + + diff --git a/android/app/res/values-ka/strings.xml b/android/app/res/values-ka/strings.xml index 592eb4f7dae..4022d72cee9 100644 --- a/android/app/res/values-ka/strings.xml +++ b/android/app/res/values-ka/strings.xml @@ -128,4 +128,14 @@ "Bluetooth აუდიო" "4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება" "Bluetooth-თან დაკავშირება" + + + + + + + + + + diff --git a/android/app/res/values-kk/strings.xml b/android/app/res/values-kk/strings.xml index 9913e3b12bd..09e4f764937 100644 --- a/android/app/res/values-kk/strings.xml +++ b/android/app/res/values-kk/strings.xml @@ -128,4 +128,14 @@ "Bluetooth aудиосы" "Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды" "Bluetooth-қа қосылу" + + + + + + + + + + diff --git a/android/app/res/values-km/strings.xml b/android/app/res/values-km/strings.xml index abf646639db..e9011530eb5 100644 --- a/android/app/res/values-km/strings.xml +++ b/android/app/res/values-km/strings.xml @@ -128,4 +128,14 @@ "សំឡេងប៊្លូធូស" "ឯកសារ​ដែល​មាន​ទំហំ​ធំ​ជាង 4 GB មិន​អាចផ្ទេរ​បាន​ទេ" "ភ្ជាប់​ប៊្លូធូស" + + + + + + + + + + diff --git a/android/app/res/values-kn/strings.xml b/android/app/res/values-kn/strings.xml index 156f83a236e..f2a7b65756f 100644 --- a/android/app/res/values-kn/strings.xml +++ b/android/app/res/values-kn/strings.xml @@ -128,4 +128,14 @@ "ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೋ" "4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್‌ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" "ಬ್ಲೂಟೂತ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ" + + + + + + + + + + diff --git a/android/app/res/values-ko/strings.xml b/android/app/res/values-ko/strings.xml index 964103ab71c..8398fe8f69a 100644 --- a/android/app/res/values-ko/strings.xml +++ b/android/app/res/values-ko/strings.xml @@ -128,4 +128,14 @@ "블루투스 오디오" "4GB보다 큰 파일은 전송할 수 없습니다" "블루투스에 연결" + + + + + + + + + + diff --git a/android/app/res/values-ky/strings.xml b/android/app/res/values-ky/strings.xml index 3aa9af4c081..e931b94e48b 100644 --- a/android/app/res/values-ky/strings.xml +++ b/android/app/res/values-ky/strings.xml @@ -128,4 +128,14 @@ "Bluetooth аудио" "4Гб чоң файлдарды өткөрүү мүмкүн эмес" "Bluetooth\'га туташуу" + + + + + + + + + + diff --git a/android/app/res/values-lo/strings.xml b/android/app/res/values-lo/strings.xml index b1eb0966e34..1904315fd29 100644 --- a/android/app/res/values-lo/strings.xml +++ b/android/app/res/values-lo/strings.xml @@ -128,4 +128,14 @@ "ສຽງ Bluetooth" "ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້" "ເຊື່ອມຕໍ່ກັບ Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-lt/strings.xml b/android/app/res/values-lt/strings.xml index d29f1033f66..8295d5a75e6 100644 --- a/android/app/res/values-lt/strings.xml +++ b/android/app/res/values-lt/strings.xml @@ -128,4 +128,14 @@ "„Bluetooth“ garsas" "Negalima perkelti didesnių nei 4 GB failų" "Prisijungti prie „Bluetooth“" + + + + + + + + + + diff --git a/android/app/res/values-lv/strings.xml b/android/app/res/values-lv/strings.xml index a250dcff4d2..840297216e7 100644 --- a/android/app/res/values-lv/strings.xml +++ b/android/app/res/values-lv/strings.xml @@ -128,4 +128,14 @@ "Bluetooth audio" "Nevar pārsūtīt failus, kas lielāki par 4 GB." "Izveidot savienojumu ar Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-mk/strings.xml b/android/app/res/values-mk/strings.xml index ee0b2007f6b..b312b4f4150 100644 --- a/android/app/res/values-mk/strings.xml +++ b/android/app/res/values-mk/strings.xml @@ -128,4 +128,14 @@ "Аудио преку Bluetooth" "Не може да се пренесуваат датотеки поголеми од 4 GB" "Поврзи се со Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ml/strings.xml b/android/app/res/values-ml/strings.xml index 25f1a128ac1..8fed95a7434 100644 --- a/android/app/res/values-ml/strings.xml +++ b/android/app/res/values-ml/strings.xml @@ -128,4 +128,14 @@ "Bluetooth ഓഡിയോ" "4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല" "Bluetooth-ലേക്ക് കണക്‌റ്റ് ചെയ്യുക" + + + + + + + + + + diff --git a/android/app/res/values-mn/strings.xml b/android/app/res/values-mn/strings.xml index 6eaec121eca..556a9b9fba0 100644 --- a/android/app/res/values-mn/strings.xml +++ b/android/app/res/values-mn/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Аудио" "4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй" "Bluetooth-тэй холбогдох" + + + + + + + + + + diff --git a/android/app/res/values-mr/strings.xml b/android/app/res/values-mr/strings.xml index a4174e3736f..62a274c0518 100644 --- a/android/app/res/values-mr/strings.xml +++ b/android/app/res/values-mr/strings.xml @@ -128,4 +128,14 @@ "ब्लूटूथ ऑडिओ" "4 GB हून मोठ्या फाइल ट्रान्सफर करता येणार नाहीत" "ब्लूटूथशी कनेक्ट करा" + + + + + + + + + + diff --git a/android/app/res/values-ms/strings.xml b/android/app/res/values-ms/strings.xml index b79a9c3cd90..6129dbc0095 100644 --- a/android/app/res/values-ms/strings.xml +++ b/android/app/res/values-ms/strings.xml @@ -128,4 +128,14 @@ "Audio Bluetooth" "Fail lebih besar daripada 4GB tidak boleh dipindahkan" "Sambung ke Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-my/strings.xml b/android/app/res/values-my/strings.xml index 692d1c03b9f..6db4716b82a 100644 --- a/android/app/res/values-my/strings.xml +++ b/android/app/res/values-my/strings.xml @@ -128,4 +128,14 @@ "ဘလူးတုသ် အသံ" "4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ" "ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်" + + + + + + + + + + diff --git a/android/app/res/values-nb/strings.xml b/android/app/res/values-nb/strings.xml index 91f1b1fb03e..8a5c824deb3 100644 --- a/android/app/res/values-nb/strings.xml +++ b/android/app/res/values-nb/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-lyd" "Filer som er større enn 4 GB, kan ikke overføres" "Koble til Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ne/strings.xml b/android/app/res/values-ne/strings.xml index a2b029a9442..a0da379bdfa 100644 --- a/android/app/res/values-ne/strings.xml +++ b/android/app/res/values-ne/strings.xml @@ -128,4 +128,14 @@ "ब्लुटुथको अडियो" "४ जि.बि. भन्दा ठूला फाइलहरूलाई स्थानान्तरण गर्न सकिँदैन" "ब्लुटुथमा कनेक्ट गर्नुहोस्" + + + + + + + + + + diff --git a/android/app/res/values-nl/strings.xml b/android/app/res/values-nl/strings.xml index f8cfb8bad50..d3eaef5141a 100644 --- a/android/app/res/values-nl/strings.xml +++ b/android/app/res/values-nl/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-audio" "Bestanden groter dan 4 GB kunnen niet worden overgedragen" "Verbinding maken met bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-or/strings.xml b/android/app/res/values-or/strings.xml index 206bde29156..2a156c7715c 100644 --- a/android/app/res/values-or/strings.xml +++ b/android/app/res/values-or/strings.xml @@ -128,4 +128,14 @@ "ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ" "4GBରୁ ବଡ଼ ଫାଇଲ୍‌ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍‌ କରାଯାଇପାରିବ ନାହିଁ" "ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ" + + + + + + + + + + diff --git a/android/app/res/values-pa/strings.xml b/android/app/res/values-pa/strings.xml index 8c0f2918f95..241e9e1389d 100644 --- a/android/app/res/values-pa/strings.xml +++ b/android/app/res/values-pa/strings.xml @@ -128,4 +128,14 @@ "ਬਲੂਟੁੱਥ ਆਡੀਓ" "4GB ਤੋਂ ਜ਼ਿਆਦਾ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ" "ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ" + + + + + + + + + + diff --git a/android/app/res/values-pl/strings.xml b/android/app/res/values-pl/strings.xml index 129805a2766..bb77d96f738 100644 --- a/android/app/res/values-pl/strings.xml +++ b/android/app/res/values-pl/strings.xml @@ -128,4 +128,14 @@ "Dźwięk Bluetooth" "Nie można przenieść plików przekraczających 4 GB" "Nawiązywanie połączeń przez Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-pt-rPT/strings.xml b/android/app/res/values-pt-rPT/strings.xml index c2c09c3013d..6ee76062d07 100644 --- a/android/app/res/values-pt-rPT/strings.xml +++ b/android/app/res/values-pt-rPT/strings.xml @@ -128,4 +128,14 @@ "Áudio Bluetooth" "Não é possível transferir os ficheiros com mais de 4 GB." "Ligar ao Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-pt/strings.xml b/android/app/res/values-pt/strings.xml index 8473c6009db..9205b7568d2 100644 --- a/android/app/res/values-pt/strings.xml +++ b/android/app/res/values-pt/strings.xml @@ -128,4 +128,14 @@ "Áudio Bluetooth" "Não é possível transferir arquivos maiores que 4 GB" "Conectar ao Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ro/strings.xml b/android/app/res/values-ro/strings.xml index c987e6dae25..5d26f0bce79 100644 --- a/android/app/res/values-ro/strings.xml +++ b/android/app/res/values-ro/strings.xml @@ -128,4 +128,14 @@ "Audio prin Bluetooth" "Fișierele mai mari de 4 GB nu pot fi transferate" "Conectează-te la Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ru/strings.xml b/android/app/res/values-ru/strings.xml index 7f206e40a54..c3ca1128f51 100644 --- a/android/app/res/values-ru/strings.xml +++ b/android/app/res/values-ru/strings.xml @@ -128,4 +128,14 @@ "Звук через Bluetooth" "Можно перенести только файлы размером до 4 ГБ." "Подключиться по Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-si/strings.xml b/android/app/res/values-si/strings.xml index 615227e66c2..5d01054c7fd 100644 --- a/android/app/res/values-si/strings.xml +++ b/android/app/res/values-si/strings.xml @@ -128,4 +128,14 @@ "බ්ලූටූත් ශ්‍රව්‍යය" "4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය" "බ්ලූටූත් වෙත සබඳින්න" + + + + + + + + + + diff --git a/android/app/res/values-sk/strings.xml b/android/app/res/values-sk/strings.xml index 1f427882b96..30e49e65e51 100644 --- a/android/app/res/values-sk/strings.xml +++ b/android/app/res/values-sk/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "Súbory väčšie ako 4 GB sa nedajú preniesť" "Pripojiť k zariadeniu Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-sl/strings.xml b/android/app/res/values-sl/strings.xml index 845d3210e1b..3a9ebb1c54a 100644 --- a/android/app/res/values-sl/strings.xml +++ b/android/app/res/values-sl/strings.xml @@ -128,4 +128,14 @@ "Zvok prek Bluetootha" "Datotek, večjih od 4 GB, ni mogoče prenesti" "Povezovanje z Bluetoothom" + + + + + + + + + + diff --git a/android/app/res/values-sq/strings.xml b/android/app/res/values-sq/strings.xml index 2d1a4b83a00..22fe0e324d6 100644 --- a/android/app/res/values-sq/strings.xml +++ b/android/app/res/values-sq/strings.xml @@ -128,4 +128,14 @@ "Audioja e \"bluetooth-it\"" "Skedarët më të mëdhenj se 4 GB nuk mund të transferohen" "Lidhu me Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-sr/strings.xml b/android/app/res/values-sr/strings.xml index b9e05b3d24e..806fb18f038 100644 --- a/android/app/res/values-sr/strings.xml +++ b/android/app/res/values-sr/strings.xml @@ -128,4 +128,14 @@ "Bluetooth аудио" "Не могу да се преносе датотеке веће од 4 GB" "Повежи са Bluetooth-ом" + + + + + + + + + + diff --git a/android/app/res/values-sv/strings.xml b/android/app/res/values-sv/strings.xml index 5c11c0b77ac..7cff7d3b8a6 100644 --- a/android/app/res/values-sv/strings.xml +++ b/android/app/res/values-sv/strings.xml @@ -128,4 +128,14 @@ "Bluetooth-ljud" "Det går inte att överföra filer som är större än 4 GB" "Anslut till Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-sw/strings.xml b/android/app/res/values-sw/strings.xml index 6b109a77f71..b9fd13896df 100644 --- a/android/app/res/values-sw/strings.xml +++ b/android/app/res/values-sw/strings.xml @@ -128,4 +128,14 @@ "Sauti ya Bluetooth" "Haiwezi kutuma faili zinazozidi GB 4" "Unganisha kwenye Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ta/strings.xml b/android/app/res/values-ta/strings.xml index e46cb874752..763bd0bdb49 100644 --- a/android/app/res/values-ta/strings.xml +++ b/android/app/res/values-ta/strings.xml @@ -128,4 +128,14 @@ "புளூடூத் ஆடியோ" "4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது" "புளூடூத் உடன் இணை" + + + + + + + + + + diff --git a/android/app/res/values-te/strings.xml b/android/app/res/values-te/strings.xml index bdca35f5e85..5b583617429 100644 --- a/android/app/res/values-te/strings.xml +++ b/android/app/res/values-te/strings.xml @@ -128,4 +128,14 @@ "బ్లూటూత్ ఆడియో" "4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు" "బ్లూటూత్‌కు కనెక్ట్ చేయి" + + + + + + + + + + diff --git a/android/app/res/values-th/strings.xml b/android/app/res/values-th/strings.xml index 5477f24f3ec..9d68fb928a1 100644 --- a/android/app/res/values-th/strings.xml +++ b/android/app/res/values-th/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้" "เชื่อมต่อบลูทูธ" + + + + + + + + + + diff --git a/android/app/res/values-tl/strings.xml b/android/app/res/values-tl/strings.xml index d4c7e1e0697..db4bb6cc7f0 100644 --- a/android/app/res/values-tl/strings.xml +++ b/android/app/res/values-tl/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "Hindi maililipat ang mga file na mas malaki sa 4GB" "Kumonekta sa Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-tr/strings.xml b/android/app/res/values-tr/strings.xml index f622f2f015b..426776260e8 100644 --- a/android/app/res/values-tr/strings.xml +++ b/android/app/res/values-tr/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Ses" "4 GB\'tan büyük dosyalar aktarılamaz" "Bluetooth\'a bağlan" + + + + + + + + + + diff --git a/android/app/res/values-uk/strings.xml b/android/app/res/values-uk/strings.xml index b00400ea386..cdaf40d3684 100644 --- a/android/app/res/values-uk/strings.xml +++ b/android/app/res/values-uk/strings.xml @@ -128,4 +128,14 @@ "Bluetooth Audio" "Не можна перенести файли, більші за 4 ГБ" "Підключитися до Bluetooth" + + + + + + + + + + diff --git a/android/app/res/values-ur/strings.xml b/android/app/res/values-ur/strings.xml index 0cfee4c29cd..3f1320ed9b5 100644 --- a/android/app/res/values-ur/strings.xml +++ b/android/app/res/values-ur/strings.xml @@ -128,4 +128,14 @@ "بلوٹوتھ آڈیو" "‏4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں" "بلوٹوتھ سے منسلک کریں" + + + + + + + + + + diff --git a/android/app/res/values-uz/strings.xml b/android/app/res/values-uz/strings.xml index a4aa29bd954..6ded769d861 100644 --- a/android/app/res/values-uz/strings.xml +++ b/android/app/res/values-uz/strings.xml @@ -128,4 +128,14 @@ "Bluetooth orqali ovoz" "4 GBdan katta hajmli videolar o‘tkazilmaydi" "Bluetoothga ulanish" + + + + + + + + + + diff --git a/android/app/res/values-vi/strings.xml b/android/app/res/values-vi/strings.xml index 53de8c750ea..bde7f18fc1f 100644 --- a/android/app/res/values-vi/strings.xml +++ b/android/app/res/values-vi/strings.xml @@ -128,4 +128,14 @@ "Â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" + + + + + + + + + + diff --git a/android/app/res/values-zh-rCN/strings.xml b/android/app/res/values-zh-rCN/strings.xml index 4133a314bad..bd6b76b6a44 100644 --- a/android/app/res/values-zh-rCN/strings.xml +++ b/android/app/res/values-zh-rCN/strings.xml @@ -128,4 +128,14 @@ "蓝牙音频" "无法传输 4GB 以上的文件" "连接到蓝牙" + + + + + + + + + + diff --git a/android/app/res/values-zh-rHK/strings.xml b/android/app/res/values-zh-rHK/strings.xml index cd33c81204d..26c1b3c6a3e 100644 --- a/android/app/res/values-zh-rHK/strings.xml +++ b/android/app/res/values-zh-rHK/strings.xml @@ -128,4 +128,14 @@ "藍牙音訊" "無法轉移 4 GB 以上的檔案" "連接藍牙" + + + + + + + + + + diff --git a/android/app/res/values-zh-rTW/strings.xml b/android/app/res/values-zh-rTW/strings.xml index a5c5b8e12a6..ee537d9ae67 100644 --- a/android/app/res/values-zh-rTW/strings.xml +++ b/android/app/res/values-zh-rTW/strings.xml @@ -128,4 +128,14 @@ "藍牙音訊" "無法轉移大於 4GB 的檔案" "使用藍牙連線" + + + + + + + + + + diff --git a/android/app/res/values-zu/strings.xml b/android/app/res/values-zu/strings.xml index 18c798f38c0..01eb255dc9d 100644 --- a/android/app/res/values-zu/strings.xml +++ b/android/app/res/values-zu/strings.xml @@ -128,4 +128,14 @@ "Umsindo we-Bluetooth" "Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa" "Xhumeka ku-Bluetooth" + + + + + + + + + + -- GitLab From dc6cf457d5993b4f8cd0d4baca0fad797167ca0c Mon Sep 17 00:00:00 2001 From: Chienyuan Date: Mon, 8 Aug 2022 19:26:39 +0800 Subject: [PATCH 076/998] Get real transport for BT_TRANSPORT_AUTO when remove bond Bug: 239723388 Test: manual Tag: #refactor Change-Id: Ia7e2e87a07fec2ad7a928eec554ca9f7a40b557d Merged-In: Ia7e2e87a07fec2ad7a928eec554ca9f7a40b557d (cherry picked from commit 1b8ec729865d53a17fd31f8f842e3c64dfc8809c) --- system/bta/dm/bta_dm_act.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc index 477702bab37..f81d1ea2eed 100644 --- a/system/bta/dm/bta_dm_act.cc +++ b/system/bta/dm/bta_dm_act.cc @@ -626,6 +626,15 @@ void bta_dm_remove_device(const RawAddress& bd_addr) { if (other_address == bd_addr) other_address = other_address2; if (other_address_connected) { + // Get real transport + if (other_transport == BT_TRANSPORT_AUTO) { + bool connected_with_br_edr = + BTM_IsAclConnectionUp(other_address, BT_TRANSPORT_BR_EDR); + other_transport = + connected_with_br_edr ? BT_TRANSPORT_BR_EDR : BT_TRANSPORT_LE; + } + LOG_INFO("other_address %s with transport %d connected", + PRIVATE_ADDRESS(other_address), other_transport); /* Take the link down first, and mark the device for removal when * disconnected */ for (int i = 0; i < bta_dm_cb.device_list.count; i++) { @@ -633,6 +642,7 @@ void bta_dm_remove_device(const RawAddress& bd_addr) { if (peer_device.peer_bdaddr == other_address && peer_device.transport == other_transport) { peer_device.conn_state = BTA_DM_UNPAIRING; + LOG_INFO("Remove ACL of address %s", PRIVATE_ADDRESS(other_address)); /* Make sure device is not in acceptlist before we disconnect */ GATT_CancelConnect(0, bd_addr, false); -- GitLab From 96f1c0d29435c096a7fb72e93bbcc54b4024af39 Mon Sep 17 00:00:00 2001 From: Stephanie Bak Date: Fri, 12 Aug 2022 21:10:08 +0000 Subject: [PATCH 077/998] Adding tests for APM enhancement Bug: 239983569 Test: atest ServiceBluetoothTests Ignore-AOSP-First: feature merged internally first to avoid merge conflicts in AOSP Change-Id: I4c73840f8c6e3a983a3c1f065733e26cf7140bf9 --- .../BluetoothAirplaneModeListenerTest.java | 163 ++++++++++++++++-- 1 file changed, 153 insertions(+), 10 deletions(-) diff --git a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java index 8c7ec8ec7f6..92c98511e98 100644 --- a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java +++ b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java @@ -16,14 +16,25 @@ package com.android.server.bluetooth; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_BT_NOTIFICATION; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_USER_TOGGLED_BLUETOOTH; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_WIFI_BT_NOTIFICATION; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.NOTIFICATION_NOT_SHOWN; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.NOTIFICATION_SHOWN; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.UNUSED; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.USED; +import static com.android.server.bluetooth.BluetoothAirplaneModeListener.WIFI_APM_STATE; + import static org.mockito.Mockito.*; -import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.os.Looper; import android.provider.Settings; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -32,23 +43,27 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @MediumTest @RunWith(AndroidJUnit4.class) public class BluetoothAirplaneModeListenerTest { - private Context mContext; + private static final String PACKAGE_NAME = "TestPackage"; + private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; - private BluetoothAdapter mBluetoothAdapter; - private BluetoothModeChangeHelper mHelper; - private BluetoothNotificationManager mBluetoothNotificationManager; - @Mock BluetoothManagerService mBluetoothManagerService; + @Mock private Context mContext; + @Mock private ContentResolver mContentResolver; + @Mock private BluetoothManagerService mBluetoothManagerService; + @Mock private BluetoothModeChangeHelper mHelper; + @Mock private BluetoothNotificationManager mBluetoothNotificationManager; + @Mock private PackageManager mPackageManager; + @Mock private Resources mResources; @Before public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - - mHelper = mock(BluetoothModeChangeHelper.class); + MockitoAnnotations.initMocks(this); + when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mHelper.getSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT)) .thenReturn(BluetoothAirplaneModeListener.MAX_TOAST_COUNT); doNothing().when(mHelper).setSettingsInt(anyString(), anyInt()); @@ -75,6 +90,59 @@ public class BluetoothAirplaneModeListenerTest { Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); } + @Test + public void testIgnoreOnAirplanModeChangeApmEnhancement() { + when(mHelper.isAirplaneModeOn()).thenReturn(true); + when(mHelper.isBluetoothOn()).thenReturn(true); + + // When APM enhancement is disabled, BT remains on when connected to a media profile + when(mHelper.getSettingsInt(APM_ENHANCEMENT)).thenReturn(0); + when(mHelper.isMediaProfileConnected()).thenReturn(true); + Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is disabled, BT turns off when not connected to a media profile + when(mHelper.isMediaProfileConnected()).thenReturn(false); + Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled but not activated by toggling BT in APM, + // BT remains on when connected to a media profile + when(mHelper.getSettingsInt(APM_ENHANCEMENT)).thenReturn(1); + when(mHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED)).thenReturn(UNUSED); + when(mHelper.isMediaProfileConnected()).thenReturn(true); + Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled but not activated by toggling BT in APM, + // BT turns off when not connected to a media profile + when(mHelper.isMediaProfileConnected()).thenReturn(false); + Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled but not activated by toggling BT in APM, + // BT remains on when the default value for BT in APM is on + when(mHelper.isBluetoothOnAPM()).thenReturn(true); + Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled but not activated by toggling BT in APM, + // BT remains off when the default value for BT in APM is off + when(mHelper.isBluetoothOnAPM()).thenReturn(false); + Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled and activated by toggling BT in APM, + // BT remains on if user's last choice in APM was on + when(mHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED)).thenReturn(USED); + when(mHelper.isBluetoothOnAPM()).thenReturn(true); + Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled and activated by toggling BT in APM, + // BT turns off if user's last choice in APM was off + when(mHelper.isBluetoothOnAPM()).thenReturn(false); + Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + + // When APM enhancement is enabled and activated by toggling BT in APM, + // BT turns off if user's last choice in APM was off even when connected to a media profile + when(mHelper.isMediaProfileConnected()).thenReturn(true); + Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); + } + @Test public void testHandleAirplaneModeChange_InvokeAirplaneModeChanged() { mBluetoothAirplaneModeListener.handleAirplaneModeChange(); @@ -109,6 +177,81 @@ public class BluetoothAirplaneModeListenerTest { verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService); } + private void setUpApmNotificationTests() throws Exception { + when(mHelper.isBluetoothOn()).thenReturn(true); + when(mHelper.isAirplaneModeOn()).thenReturn(true); + when(mHelper.isBluetoothOnAPM()).thenReturn(true); + when(mHelper.getSettingsInt(APM_ENHANCEMENT)).thenReturn(1); + when(mHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED)).thenReturn(USED); + when(mHelper.getBluetoothPackageName()).thenReturn(PACKAGE_NAME); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getResourcesForApplication(PACKAGE_NAME)).thenReturn(mResources); + } + + @Test + public void testHandleAirplaneModeChange_ShowBtAndWifiApmNotification() throws Exception { + setUpApmNotificationTests(); + when(mHelper.getSettingsInt(Settings.Global.WIFI_ON)).thenReturn(1); + when(mHelper.getSettingsSecureInt(WIFI_APM_STATE, 0)).thenReturn(1); + when(mHelper.getSettingsSecureInt(APM_WIFI_BT_NOTIFICATION, NOTIFICATION_NOT_SHOWN)) + .thenReturn(NOTIFICATION_NOT_SHOWN); + + mBluetoothAirplaneModeListener.handleAirplaneModeChange(); + + verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, + BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); + verify(mBluetoothNotificationManager).sendApmNotification(any(), any()); + verify(mHelper).setSettingsSecureInt(APM_WIFI_BT_NOTIFICATION, NOTIFICATION_SHOWN); + } + + @Test + public void testHandleAirplaneModeChange_NotShowBtAndWifiApmNotification() throws Exception { + setUpApmNotificationTests(); + when(mHelper.getSettingsInt(Settings.Global.WIFI_ON)).thenReturn(1); + when(mHelper.getSettingsSecureInt(WIFI_APM_STATE, 0)).thenReturn(1); + when(mHelper.getSettingsSecureInt(APM_WIFI_BT_NOTIFICATION, NOTIFICATION_NOT_SHOWN)) + .thenReturn(NOTIFICATION_SHOWN); + + mBluetoothAirplaneModeListener.handleAirplaneModeChange(); + + verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, + BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); + verify(mBluetoothNotificationManager, never()).sendApmNotification(any(), any()); + verify(mHelper, never()).setSettingsSecureInt(APM_WIFI_BT_NOTIFICATION, NOTIFICATION_SHOWN); + } + + @Test + public void testHandleAirplaneModeChange_ShowBtApmNotification() throws Exception { + setUpApmNotificationTests(); + when(mHelper.getSettingsInt(Settings.Global.WIFI_ON)).thenReturn(1); + when(mHelper.getSettingsSecureInt(WIFI_APM_STATE, 0)).thenReturn(0); + when(mHelper.getSettingsSecureInt(APM_BT_NOTIFICATION, NOTIFICATION_NOT_SHOWN)) + .thenReturn(NOTIFICATION_NOT_SHOWN); + + mBluetoothAirplaneModeListener.handleAirplaneModeChange(); + + verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, + BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); + verify(mBluetoothNotificationManager).sendApmNotification(any(), any()); + verify(mHelper).setSettingsSecureInt(APM_BT_NOTIFICATION, NOTIFICATION_SHOWN); + } + + @Test + public void testHandleAirplaneModeChange_NotShowBtApmNotification() throws Exception { + setUpApmNotificationTests(); + when(mHelper.getSettingsInt(Settings.Global.WIFI_ON)).thenReturn(1); + when(mHelper.getSettingsSecureInt(WIFI_APM_STATE, 0)).thenReturn(0); + when(mHelper.getSettingsSecureInt(APM_BT_NOTIFICATION, NOTIFICATION_NOT_SHOWN)) + .thenReturn(NOTIFICATION_SHOWN); + + mBluetoothAirplaneModeListener.handleAirplaneModeChange(); + + verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, + BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); + verify(mBluetoothNotificationManager, never()).sendApmNotification(any(), any()); + verify(mHelper, never()).setSettingsSecureInt(APM_BT_NOTIFICATION, NOTIFICATION_SHOWN); + } + @Test public void testIsPopToast_PopToast() { mBluetoothAirplaneModeListener.mToastCount = 0; -- GitLab From 2c09d202c83af6a511ad29ead37f4055760f095f Mon Sep 17 00:00:00 2001 From: Brian Delwiche Date: Wed, 10 Aug 2022 06:47:52 +0000 Subject: [PATCH 078/998] Add buffer in pin_reply in bluetooth.cc Bug: 228602963 Test: make Tag: #security Ignore-AOSP-First: Security Change-Id: Ibd7d9041e528262537d175d0faa6d2ed16e7dceb --- system/btif/src/bluetooth.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc index a251067b1cd..2be88b214d4 100644 --- a/system/btif/src/bluetooth.cc +++ b/system/btif/src/bluetooth.cc @@ -398,11 +398,14 @@ static int get_connection_state(const RawAddress* bd_addr) { static int pin_reply(const RawAddress* bd_addr, uint8_t accept, uint8_t pin_len, bt_pin_code_t* pin_code) { + bt_pin_code_t tmp_pin_code; if (!interface_ready()) return BT_STATUS_NOT_READY; if (pin_code == nullptr || pin_len > PIN_CODE_LEN) return BT_STATUS_FAIL; + memcpy(&tmp_pin_code, pin_code, pin_len); + do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_pin_reply, *bd_addr, - accept, pin_len, *pin_code)); + accept, pin_len, tmp_pin_code)); return BT_STATUS_SUCCESS; } -- GitLab From 693e18805b2e0db4f7dac7ad25e69d8e28120067 Mon Sep 17 00:00:00 2001 From: Brian Delwiche Date: Wed, 10 Aug 2022 06:47:52 +0000 Subject: [PATCH 079/998] Add buffer in pin_reply in bluetooth.cc Bug: 228602963 Test: make Tag: #security Ignore-AOSP-First: Security Change-Id: Ibd7d9041e528262537d175d0faa6d2ed16e7dceb --- system/btif/src/bluetooth.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc index d40ad927a9c..58d6da0c1d9 100644 --- a/system/btif/src/bluetooth.cc +++ b/system/btif/src/bluetooth.cc @@ -398,11 +398,14 @@ static int get_connection_state(const RawAddress* bd_addr) { static int pin_reply(const RawAddress* bd_addr, uint8_t accept, uint8_t pin_len, bt_pin_code_t* pin_code) { + bt_pin_code_t tmp_pin_code; if (!interface_ready()) return BT_STATUS_NOT_READY; if (pin_code == nullptr || pin_len > PIN_CODE_LEN) return BT_STATUS_FAIL; + memcpy(&tmp_pin_code, pin_code, pin_len); + do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_pin_reply, *bd_addr, - accept, pin_len, *pin_code)); + accept, pin_len, tmp_pin_code)); return BT_STATUS_SUCCESS; } -- GitLab From a0e5e4489539ff59fa1ef697f0c5ed9c10e2190e Mon Sep 17 00:00:00 2001 From: Chris Manton Date: Wed, 10 Aug 2022 08:10:02 -0700 Subject: [PATCH 080/998] Fix address_with_type inequality comparison operator< Bug: 242054540 Test: gd/cert/run Tag: #refactor BYPASS_LONG_LINES_REASON: Bluetooth likes 120 lines Ignore-AOSP-First: cherry-pick Merged-In: I1412d795330c5ee593daab82efdc3d96324f6f97 Change-Id: I1412d795330c5ee593daab82efdc3d96324f6f97 --- system/gd/hci/address_with_type.h | 2 +- system/gd/hci/address_with_type_test.cc | 141 +++++++++++++++++++++++- 2 files changed, 139 insertions(+), 4 deletions(-) diff --git a/system/gd/hci/address_with_type.h b/system/gd/hci/address_with_type.h index 48336ffa3af..ec4d40c63a6 100644 --- a/system/gd/hci/address_with_type.h +++ b/system/gd/hci/address_with_type.h @@ -73,7 +73,7 @@ class AddressWithType final { } bool operator<(const AddressWithType& rhs) const { - return address_ < rhs.address_ && address_type_ < rhs.address_type_; + return (address_ != rhs.address_) ? address_ < rhs.address_ : address_type_ < rhs.address_type_; } bool operator==(const AddressWithType& rhs) const { return address_ == rhs.address_ && address_type_ == rhs.address_type_; diff --git a/system/gd/hci/address_with_type_test.cc b/system/gd/hci/address_with_type_test.cc index 37b4ab69862..efa2f13addd 100644 --- a/system/gd/hci/address_with_type_test.cc +++ b/system/gd/hci/address_with_type_test.cc @@ -16,12 +16,14 @@ * ******************************************************************************/ -#include +#include "hci/address_with_type.h" #include +#include +#include + #include "hci/address.h" -#include "hci/address_with_type.h" #include "hci/hci_packets.h" namespace bluetooth { @@ -97,5 +99,138 @@ TEST(AddressWithTypeTest, IsRpaThatMatchesIrk) { EXPECT_FALSE(address_2.IsRpaThatMatchesIrk(irk_1)); } +TEST(AddressWithTypeTest, OperatorLessThan) { + { + AddressWithType address_1 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDD}}, AddressType::RANDOM_DEVICE_ADDRESS); + + ASSERT_TRUE(address_2 < address_1); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x70, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + + ASSERT_TRUE(address_1 < address_2); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x70, 0x02, 0x03, 0xC9, 0x12, 0xDD}}, AddressType::RANDOM_DEVICE_ADDRESS); + + ASSERT_TRUE(address_1 < address_2); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::RANDOM_DEVICE_ADDRESS); + + ASSERT_TRUE(address_1 < address_2); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + + ASSERT_FALSE(address_1 < address_2); + } +} + +TEST(AddressWithTypeTest, OrderedMap) { + std::map map; + + { + AddressWithType address_1 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x70, 0x02, 0x03, 0xC9, 0x12, 0xDD}}, AddressType::RANDOM_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(2UL, map.size()); + map.clear(); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(2UL, map.size()); + map.clear(); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(1UL, map.size()); + map.clear(); + } +} + +TEST(AddressWithTypeTest, HashMap) { + std::unordered_map map; + + { + AddressWithType address_1 = + AddressWithType(Address{{0x50, 0x02, 0x03, 0xC9, 0x12, 0xDE}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x70, 0x02, 0x03, 0xC9, 0x12, 0xDD}}, AddressType::RANDOM_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(2UL, map.size()); + map.clear(); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::RANDOM_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(2UL, map.size()); + map.clear(); + } + + { + AddressWithType address_1 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + AddressWithType address_2 = + AddressWithType(Address{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, AddressType::PUBLIC_DEVICE_ADDRESS); + + map[address_1] = 1; + map[address_2] = 2; + + ASSERT_EQ(1UL, map.size()); + map.clear(); + } +} + } // namespace hci -} // namespace bluetooth \ No newline at end of file +} // namespace bluetooth -- GitLab From ca6cac4996a2a8c5369c646648ccbc49e29658c6 Mon Sep 17 00:00:00 2001 From: Brian Delwiche Date: Wed, 10 Aug 2022 05:01:58 +0000 Subject: [PATCH 081/998] Add local to pan_api.cc Bug: 233604485 Test: Manual, connected BT and played audio Tag: #security Ignore-AOSP-First: Security Change-Id: Ibb7fffccde731ecb7c12b8ddf212f02f156ffd89 --- system/stack/pan/pan_api.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/stack/pan/pan_api.cc b/system/stack/pan/pan_api.cc index 0352ed4158d..40b26a3acae 100644 --- a/system/stack/pan/pan_api.cc +++ b/system/stack/pan/pan_api.cc @@ -541,6 +541,7 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst, return PAN_FAILURE; } + uint16_t len = p_buf->len; result = BNEP_WriteBuf(pcb->handle, dst, p_buf, protocol, &src, ext); if (result == BNEP_IGNORE_CMD) { PAN_TRACE_DEBUG("PAN ignored data buf write to PANU"); @@ -552,7 +553,7 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst, return (tPAN_RESULT)result; } - pcb->write.octets += p_buf->len; + pcb->write.octets += len; pcb->write.packets++; PAN_TRACE_DEBUG("PAN successfully sent data buf to the PANU"); -- GitLab From 4ce45deae4cf47ebcbe788608b15d88e53e799d7 Mon Sep 17 00:00:00 2001 From: Brian Delwiche Date: Wed, 10 Aug 2022 05:01:58 +0000 Subject: [PATCH 082/998] Add local to pan_api.cc Bug: 233604485 Test: Manual, connected BT and played audio Tag: #security Ignore-AOSP-First: Security Change-Id: Ibb7fffccde731ecb7c12b8ddf212f02f156ffd89 --- system/stack/pan/pan_api.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/stack/pan/pan_api.cc b/system/stack/pan/pan_api.cc index 0352ed4158d..40b26a3acae 100644 --- a/system/stack/pan/pan_api.cc +++ b/system/stack/pan/pan_api.cc @@ -541,6 +541,7 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst, return PAN_FAILURE; } + uint16_t len = p_buf->len; result = BNEP_WriteBuf(pcb->handle, dst, p_buf, protocol, &src, ext); if (result == BNEP_IGNORE_CMD) { PAN_TRACE_DEBUG("PAN ignored data buf write to PANU"); @@ -552,7 +553,7 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst, return (tPAN_RESULT)result; } - pcb->write.octets += p_buf->len; + pcb->write.octets += len; pcb->write.packets++; PAN_TRACE_DEBUG("PAN successfully sent data buf to the PANU"); -- GitLab From d6647319ee12eb8a6323daaa86291f1b859d146a Mon Sep 17 00:00:00 2001 From: Greg Kaiser Date: Thu, 11 Aug 2022 06:58:28 -0700 Subject: [PATCH 083/998] Catch errors from opus_encode() call opus_encode() uses negative return values to indicate errors, so we store the result in a signed integer so we can properly detect these errors. Bug: 226441860 Test: TreeHugger Change-Id: I674c66f146ba86cd12099e8639043042448321fc --- system/stack/a2dp/a2dp_vendor_opus_encoder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/stack/a2dp/a2dp_vendor_opus_encoder.cc b/system/stack/a2dp/a2dp_vendor_opus_encoder.cc index ded13e4ae9c..e5dc8709563 100644 --- a/system/stack/a2dp/a2dp_vendor_opus_encoder.cc +++ b/system/stack/a2dp/a2dp_vendor_opus_encoder.cc @@ -379,7 +379,7 @@ static void a2dp_opus_encode_frames(uint8_t nb_frame) { p_encoder_params->channel_mode]; int32_t out_frames = 0; - uint32_t written = 0; + int32_t written = 0; uint32_t bytes_read = 0; while (nb_frame) { -- GitLab From 2a5372b1f9bae86a8d491ae26e505fcb9ab2783a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Rymanowski?= Date: Wed, 3 Aug 2022 06:47:20 +0000 Subject: [PATCH 084/998] l2cap: Add proper handling of L2CAP Reject command This patch fixes scenario when Android is wating for Credit Based Connection Response, but remote side does not undestand the command. It also fix handling Credit Based Connection Response Negative Bug: 237399985 Test: atest BluetoothInstrumentationTests Test: L2CAP/ECFC/BV-01 Tag: #feature Merged-In: I02d63ea405cb7b6e6bc6d7a30e810be2c0a47805 Change-Id: I02d63ea405cb7b6e6bc6d7a30e810be2c0a47805 (cherry picked from commit 83bcc56c2a06ee61566337afcf8ac68d25bfa7a9) --- system/stack/l2cap/l2c_ble.cc | 45 +++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc index 67ccd01a762..8a99f30e064 100755 --- a/system/stack/l2cap/l2c_ble.cc +++ b/system/stack/l2cap/l2c_ble.cc @@ -409,6 +409,30 @@ void l2cble_process_conn_update_evt(uint16_t handle, uint8_t status, p_lcb->conn_update_mask); } +/******************************************************************************* + * + * Function l2cble_handle_connect_rsp_neg + * + * Description This function sends error message to all the + * outstanding channels + * + * Returns void + * + ******************************************************************************/ +static void l2cble_handle_connect_rsp_neg(tL2C_LCB* p_lcb, + tL2C_CONN_INFO* con_info) { + tL2C_CCB* temp_p_ccb = NULL; + for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) { + uint16_t cid = p_lcb->pending_ecoc_connection_cids[i]; + temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid); + l2c_csm_execute(temp_p_ccb, L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG, + con_info); + } + + p_lcb->pending_ecoc_conn_cnt = 0; + memset(p_lcb->pending_ecoc_connection_cids, 0, L2CAP_CREDIT_BASED_MAX_CIDS); +} + /******************************************************************************* * * Function l2cble_process_sig_cmd @@ -452,9 +476,16 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { } switch (cmd_code) { - case L2CAP_CMD_REJECT: - p += 2; - break; + case L2CAP_CMD_REJECT: { + uint16_t reason; + STREAM_TO_UINT16(reason, p); + + if (reason == L2CAP_CMD_REJ_NOT_UNDERSTOOD && + p_lcb->pending_ecoc_conn_cnt > 0) { + con_info.l2cap_result = L2CAP_LE_RESULT_NO_PSM; + l2cble_handle_connect_rsp_neg(p_lcb, &con_info); + } + } break; case L2CAP_CMD_ECHO_REQ: case L2CAP_CMD_ECHO_RSP: @@ -680,8 +711,9 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { con_info.l2cap_result == L2CAP_LE_RESULT_INSUFFICIENT_AUTHORIZATION || con_info.l2cap_result == L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS || con_info.l2cap_result == L2CAP_LE_RESULT_INVALID_PARAMETERS) { - l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG, - &con_info); + L2CAP_TRACE_ERROR("L2CAP - not accepted. Status %d", + con_info.l2cap_result); + l2cble_handle_connect_rsp_neg(p_lcb, &con_info); return; } @@ -690,8 +722,7 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { mps < L2CAP_CREDIT_BASED_MIN_MPS || mps > L2CAP_LE_MAX_MPS) { L2CAP_TRACE_ERROR("L2CAP - invalid params"); con_info.l2cap_result = L2CAP_LE_RESULT_INVALID_PARAMETERS; - l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG, - &con_info); + l2cble_handle_connect_rsp_neg(p_lcb, &con_info); return; } -- GitLab From 987d0e24b6d5a69d899fb4540a3dd470c382f891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Rymanowski?= Date: Wed, 3 Aug 2022 06:38:29 +0000 Subject: [PATCH 085/998] leaudio: Add priority to ringtone context type Since ringtone is actually preparation for the conversation and we are using configuration as a conversation, make sure it will not be changed to media. This patch also improves logging a bit Bug: 233961821 Test: atest BluetoothInstrumentationTests Test: phone call Tag: #feature Merged-In: Id624a1a331a7dadf3e2779cfde7504aa1b926abe Change-Id: Id624a1a331a7dadf3e2779cfde7504aa1b926abe (cherry picked from commit 59fb5f0db42c83e86189adf0fc2aa5998cd4333e) --- system/bta/le_audio/client.cc | 76 ++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc index f12a0781395..f21d0b4ab76 100644 --- a/system/bta/le_audio/client.cc +++ b/system/bta/le_audio/client.cc @@ -141,6 +141,72 @@ std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) { return os; } +static std::string usageToString(audio_usage_t usage) { + switch (usage) { + case AUDIO_USAGE_UNKNOWN: + return "USAGE_UNKNOWN"; + case AUDIO_USAGE_MEDIA: + return "USAGE_MEDIA"; + case AUDIO_USAGE_VOICE_COMMUNICATION: + return "USAGE_VOICE_COMMUNICATION"; + case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING: + return "USAGE_VOICE_COMMUNICATION_SIGNALLING"; + case AUDIO_USAGE_ALARM: + return "USAGE_ALARM"; + case AUDIO_USAGE_NOTIFICATION: + return "USAGE_NOTIFICATION"; + case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE: + return "USAGE_NOTIFICATION_TELEPHONY_RINGTONE"; + case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST: + return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST"; + case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT"; + case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED: + return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED"; + case AUDIO_USAGE_NOTIFICATION_EVENT: + return "USAGE_NOTIFICATION_EVENT"; + case AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY: + return "USAGE_ASSISTANCE_ACCESSIBILITY"; + case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"; + case AUDIO_USAGE_ASSISTANCE_SONIFICATION: + return "USAGE_ASSISTANCE_SONIFICATION"; + case AUDIO_USAGE_GAME: + return "USAGE_GAME"; + case AUDIO_USAGE_ASSISTANT: + return "USAGE_ASSISTANT"; + case AUDIO_USAGE_CALL_ASSISTANT: + return "USAGE_CALL_ASSISTANT"; + case AUDIO_USAGE_EMERGENCY: + return "USAGE_EMERGENCY"; + case AUDIO_USAGE_SAFETY: + return "USAGE_SAFETY"; + case AUDIO_USAGE_VEHICLE_STATUS: + return "USAGE_VEHICLE_STATUS"; + case AUDIO_USAGE_ANNOUNCEMENT: + return "USAGE_ANNOUNCEMENT"; + default: + return "unknown usage "; + } +} + +static std::string contentTypeToString(audio_content_type_t content_type) { + switch (content_type) { + case AUDIO_CONTENT_TYPE_UNKNOWN: + return "CONTENT_TYPE_UNKNOWN"; + case AUDIO_CONTENT_TYPE_SPEECH: + return "CONTENT_TYPE_SPEECH"; + case AUDIO_CONTENT_TYPE_MUSIC: + return "CONTENT_TYPE_MUSIC"; + case AUDIO_CONTENT_TYPE_MOVIE: + return "CONTENT_TYPE_MOVIE"; + case AUDIO_CONTENT_TYPE_SONIFICATION: + return "CONTENT_TYPE_SONIFICATION"; + default: + return "unknown content type "; + } +} + namespace { void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); @@ -3217,6 +3283,10 @@ class LeAudioClientImpl : public LeAudioClient { if (iter != available_contents.end()) return LeAudioContextType::CONVERSATIONAL; + iter = find(available_contents.begin(), available_contents.end(), + LeAudioContextType::RINGTONE); + if (iter != available_contents.end()) return LeAudioContextType::RINGTONE; + iter = find(available_contents.begin(), available_contents.end(), LeAudioContextType::GAME); if (iter != available_contents.end()) return LeAudioContextType::GAME; @@ -3289,8 +3359,10 @@ class LeAudioClientImpl : public LeAudioClient { continue; } - LOG_INFO("%s: usage=%d, content_type=%d, gain=%f", __func__, - tracks->usage, tracks->content_type, tracks->gain); + LOG_INFO("%s: usage=%s(%d), content_type=%s(%d), gain=%f", __func__, + usageToString(tracks->usage).c_str(), tracks->usage, + contentTypeToString(tracks->content_type).c_str(), + tracks->content_type, tracks->gain); auto new_context = AudioContentToLeAudioContext(tracks->content_type, tracks->usage); -- GitLab From a88fe05e3d296b0fd15f974c07fccd229bbfcc20 Mon Sep 17 00:00:00 2001 From: Etienne Ruffieux Date: Mon, 1 Aug 2022 20:14:19 -0700 Subject: [PATCH 086/998] [PANDORA_TEST] Add GATT/CL/GAD tests Tag: #feature Test: atest pts-bot:GATT/CL/GAD Bug: 239103399 Change-Id: Ie8533203eb061177792517935a7963aa4171f99f (cherry picked from commit 0636fed429c0bbee6e678192a875d0709d8918c2) Merged-In: Ie8533203eb061177792517935a7963aa4171f99f --- android/pandora/mmi2grpc/mmi2grpc/gatt.py | 426 +++++++++++++++++- android/pandora/server/configs/PtsBotTest.xml | 1 + .../server/configs/pts_bot_tests_config.json | 14 +- .../pandora/server/proto/pandora/gatt.proto | 72 ++- .../pandora/server/proto/pandora/host.proto | 10 +- .../server/src/com/android/pandora/Gatt.kt | 129 +++++- .../server/src/com/android/pandora/Host.kt | 2 +- 7 files changed, 632 insertions(+), 22 deletions(-) diff --git a/android/pandora/mmi2grpc/mmi2grpc/gatt.py b/android/pandora/mmi2grpc/mmi2grpc/gatt.py index d03384c6f79..f997494e0a8 100644 --- a/android/pandora/mmi2grpc/mmi2grpc/gatt.py +++ b/android/pandora/mmi2grpc/mmi2grpc/gatt.py @@ -20,6 +20,12 @@ from mmi2grpc._proxy import ProfileProxy from pandora.gatt_grpc import GATT from pandora.host_grpc import Host +# Tests that need GATT cache cleared before discovering services. +NEEDS_CACHE_CLEARED = { + "GATT/CL/GAD/BV-01-C", + "GATT/CL/GAD/BV-06-C", +} + class GATTProxy(ProfileProxy): @@ -28,9 +34,12 @@ class GATTProxy(ProfileProxy): self.gatt = GATT(channel) self.host = Host(channel) self.connection = None + self.services = None + self.characteristics = None + self.descriptors = None @assert_description - def MMI_IUT_INITIATE_CONNECTION(self, pts_addr: bytes, **kwargs): + def MMI_IUT_INITIATE_CONNECTION(self, test, pts_addr: bytes, **kwargs): """ Please initiate a GATT connection to the PTS. @@ -40,6 +49,26 @@ class GATTProxy(ProfileProxy): """ self.connection = self.host.ConnectLE(address=pts_addr).connection + if test in NEEDS_CACHE_CLEARED: + self.gatt.ClearCache(connection=self.connection) + return "OK" + + @assert_description + def MMI_IUT_INITIATE_DISCONNECTION(self, **kwargs): + """ + Please initiate a GATT disconnection to the PTS. + + Description: Verify + that the Implementation Under Test (IUT) can initiate GATT disconnect + request to PTS. + """ + + assert self.connection is not None + self.host.DisconnectLE(connection=self.connection) + self.connection = None + self.services = None + self.characteristics = None + self.descriptors = None return "OK" @assert_description @@ -58,7 +87,7 @@ class GATTProxy(ProfileProxy): def MMI_IUT_SEND_PREPARE_WRITE_REQUEST_VALID_SIZE(self, description: str, **kwargs): """ - Please send prepare write request with handle = 'FFFF'O and size = 'XXX' + Please send prepare write request with handle = 'XXXX'O and size = 'XXX' to the PTS. Description: Verify that the Implementation Under Test @@ -69,20 +98,399 @@ class GATTProxy(ProfileProxy): matches = re.findall("'([a0-Z9]*)'O and size = '([a0-Z9]*)'", description) handle = int(matches[0][0], 16) data = bytes([1]) * int(matches[0][1]) - self.gatt.WriteCharacteristicFromHandle(connection=self.connection, handle=handle, value=data) + self.gatt.WriteCharacteristicFromHandle(connection=self.connection,\ + handle=handle, value=data) return "OK" @assert_description - def MMI_IUT_INITIATE_DISCONNECTION(self, **kwargs): + def MMI_IUT_DISCOVER_PRIMARY_SERVICES(self, **kwargs): + """ + Please send discover all primary services command to the PTS. + Description: Verify that the Implementation Under Test (IUT) can send + Discover All Primary Services. + """ + + assert self.connection is not None + self.services = self.gatt.DiscoverServices(connection=self.connection).services + return "OK" + + def MMI_SEND_PRIMARY_SERVICE_UUID(self, description: str, **kwargs): + """ + Please send discover primary services with UUID value set to 'XXXX'O to + the PTS. + + Description: Verify that the Implementation Under Test (IUT) + can send Discover Primary Services UUID = 'XXXX'O. + """ + + assert self.connection is not None + uuid = formatUuid(re.findall("'([a0-Z9]*)'O", description)[0]) + self.services = self.gatt.DiscoverServiceByUuid(connection=self.connection,\ + uuid=uuid).services + return "OK" + + def MMI_SEND_PRIMARY_SERVICE_UUID_128(self, description: str, **kwargs): + """ + Please send discover primary services with UUID value set to + 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX'O to the PTS. + + Description: + Verify that the Implementation Under Test (IUT) can send Discover + Primary Services UUID = 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX'O. + """ + + assert self.connection is not None + uuid = formatUuid(re.findall("'([a0-Z9-]*)'O", description)[0]) + self.services = self.gatt.DiscoverServiceByUuid(connection=self.connection,\ + uuid=uuid).services + return "OK" + + def MMI_CONFIRM_PRIMARY_SERVICE_UUID(self, **kwargs): + """ + Please confirm IUT received primary services uuid = 'XXXX'O , Service + start handle = 'XXXX'O, end handle = 'XXXX'O in database. Click Yes if + IUT received it, otherwise click No. + + Description: Verify that the + Implementation Under Test (IUT) can send Discover primary service by + UUID in database. + """ + + # Android doesn't store services discovered by UUID. + return "Yes" + + @assert_description + def MMI_CONFIRM_NO_PRIMARY_SERVICE_SMALL(self, **kwargs): + """ + Please confirm that IUT received NO service uuid found in the small + database file. Click Yes if NO service found, otherwise click No. + Description: Verify that the Implementation Under Test (IUT) can send + Discover primary service by UUID in small database. """ - Please initiate a GATT disconnection to the PTS. + + # Android doesn't store services discovered by UUID. + return "Yes" + + def MMI_CONFIRM_PRIMARY_SERVICE_UUID_128(self, **kwargs): + """ + Please confirm IUT received primary services uuid= + 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX'O, Service start handle = + 'XXXX'O, end handle = 'XXXX'O in database. Click Yes if IUT received it, + otherwise click No. + + Description: Verify that the Implementation Under + Test (IUT) can send Discover primary service by UUID in database. + """ + + # Android doesn't store services discovered by UUID. + return "Yes" + + def MMI_CONFIRM_PRIMARY_SERVICE(self, description: str, **kwargs): + """ + Please confirm IUT received primary services Primary Service = 'XXXX'O + Primary Service = 'XXXX'O in database. Click Yes if IUT received it, + otherwise click No. + + Description: Verify that the Implementation Under + Test (IUT) can send Discover all primary services in database. + """ + + assert self.services is not None + all_matches = list(map(formatUuid, re.findall("'([a0-Z9]*)'O", description))) + assert all(uuid in list(map(lambda service: service.uuid, self.services))\ + for uuid in all_matches) + return "OK" + + @assert_description + def MMI_IUT_FIND_INCLUDED_SERVICES(self, **kwargs): + """ + Please send discover all include services to the PTS to discover all + Include Service supported in the PTS. Discover primary service if + needed. + + Description: Verify that the Implementation Under Test (IUT) + can send Discover all include services command. + """ + + assert self.connection is not None + self.services = self.gatt.DiscoverServices(connection=self.connection).services + return "OK" + + @assert_description + def MMI_CONFIRM_NO_INCLUDE_SERVICE(self, **kwargs): + """ + There is no include service in the database file. Description: Verify - that the Implementation Under Test (IUT) can initiate GATT disconnect - request to PTS. + that the Implementation Under Test (IUT) can send Discover all include + services in database. """ assert self.connection is not None - self.host.DisconnectLE(connection=self.connection) - self.connection = None + assert self.services is not None + for service in self.services: + assert len(service.included_services) is 0 return "OK" + + def MMI_CONFIRM_INCLUDE_SERVICE(self, description: str, **kwargs): + """ + Please confirm IUT received include services: + + Attribute Handle = 'XXXX'O, Included Service Attribute handle = 'XXXX'O, + End Group Handle = 'XXXX'O, Service UUID = 'XXXX'O + + Click Yes if IUT received it, otherwise click No. + + Description: Verify + that the Implementation Under Test (IUT) can send Discover all include + services in database. + """ + + assert self.connection is not None + assert self.services is not None + """ + Number of checks can vary but information is always the same, + so we need to iterate through the services and check if its included + services match one of these. + """ + all_matches = re.findall("'([a0-Z9]*)'O", description) + found_services = 0 + for service in self.services: + for i in range(0, len(all_matches), 4): + if compareIncludedServices(service,\ + (stringHandleToInt(all_matches[i])),\ + stringHandleToInt(all_matches[i + 1]),\ + formatUuid(all_matches[i + 3])): + found_services += 1 + assert found_services == (len(all_matches) / 4) + return "OK" + + def MMI_IUT_DISCOVER_SERVICE_UUID(self, description: str, **kwargs): + """ + Discover all characteristics of service UUID= 'XXXX'O, Service start + handle = 'XXXX'O, end handle = 'XXXX'O. + + Description: Verify that the + Implementation Under Test (IUT) can send Discover all charactieristics + of a service. + """ + + assert self.connection is not None + service_uuid = formatUuid(re.findall("'([a0-Z9]*)'O", description)[0]) + self.services = self.gatt.DiscoverServices(connection=self.connection).services + self.characteristics = getCharacteristicsForServiceUuid(self.services, service_uuid) + return "OK" + + def MMI_CONFIRM_ALL_CHARACTERISTICS_SERVICE(self, description: str, **kwargs): + """ + Please confirm IUT received all characteristics of service + handle='XXXX'O handle='XXXX'O handle='XXXX'O handle='XXXX'O + handle='XXXX'O handle='XXXX'O handle='XXXX'O handle='XXXX'O + handle='XXXX'O handle='XXXX'O handle='XXXX'O in database. Click Yes if + IUT received it, otherwise click No. + + Description: Verify that the + Implementation Under Test (IUT) can send Discover all characteristics of + a service in database. + """ + + assert self.characteristics is not None + all_matches = list(map(stringCharHandleToInt, re.findall("'([a0-Z9]*)'O", description))) + assert all(handle in list(map(lambda char: char.handle, self.characteristics))\ + for handle in all_matches) + return "Yes" + + def MMI_IUT_DISCOVER_SERVICE_UUID_RANGE(self, description: str, **kwargs): + """ + Please send discover characteristics by UUID. Range start from handle = + 'XXXX'O end handle = 'XXXX'O characteristics UUID = 0xXXXX'O. + Description: Verify that the Implementation Under Test (IUT) can send + Discover characteristics by UUID. + """ + + assert self.connection is not None + handles = re.findall("'([a0-Z9]*)'O", description) + """ + PTS sends UUIDS description formatted differently in this MMI, + so we need to check for each known format. + """ + uuid_match = re.findall("0x([a0-Z9]*)'O", description) + if len(uuid_match) == 0: + uuid_match = re.search("UUID = (.*)'O", description) + uuid = formatUuid(uuid_match[1]) + else: + uuid = formatUuid(uuid_match[0]) + self.services = self.gatt.DiscoverServices(connection=self.connection).services + self.characteristics = getCharacteristicsRange(self.services,\ + stringHandleToInt(handles[0]), stringHandleToInt(handles[1]), uuid) + return "OK" + + def MMI_CONFIRM_CHARACTERISTICS(self, description: str, **kwargs): + """ + Please confirm IUT received characteristic handle='XXXX'O UUID='XXXX'O + in database. Click Yes if IUT received it, otherwise click No. + Description: Verify that the Implementation Under Test (IUT) can send + Discover primary service by UUID in database. + """ + + assert self.characteristics is not None + all_matches = re.findall("'([a0-Z9-]*)'O", description) + for characteristic in self.characteristics: + if characteristic.handle == stringHandleToInt(all_matches[0])\ + and characteristic.uuid == formatUuid(all_matches[1]): + return "Yes" + return "No" + + @assert_description + def MMI_CONFIRM_NO_CHARACTERISTICSUUID_SMALL(self, **kwargs): + """ + Please confirm that IUT received NO 128 bit uuid in the small database + file. Click Yes if NO handle found, otherwise click No. + + Description: + Verify that the Implementation Under Test (IUT) can discover + characteristics by UUID in small database. + """ + + assert self.characteristics is not None + assert len(self.characteristics) == 0 + return "OK" + + def MMI_IUT_DISCOVER_DESCRIPTOR_RANGE(self, description: str, **kwargs): + """ + Please send discover characteristics descriptor range start from handle + = 'XXXX'O end handle = 'XXXX'O to the PTS. + + Description: Verify that the + Implementation Under Test (IUT) can send Discover characteristics + descriptor. + """ + + assert self.connection is not None + handles = re.findall("'([a0-Z9]*)'O", description) + self.services = self.gatt.DiscoverServices(connection=self.connection).services + self.descriptors = getDescriptorsRange(self.services,\ + stringHandleToInt(handles[0]), stringHandleToInt(handles[1])) + return "OK" + + def MMI_CONFIRM_CHARACTERISTICS_DESCRIPTORS(self, description: str, **kwargs): + """ + Please confirm IUT received characteristic descriptors handle='XXXX'O + UUID=0xXXXX in database. Click Yes if IUT received it, otherwise click + No. + + Description: Verify that the Implementation Under Test (IUT) can + send Discover characteristic descriptors in database. + """ + + assert self.descriptors is not None + handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) + uuid = formatUuid(re.search("UUID=0x(.*) ", description)[1]) + for descriptor in self.descriptors: + if descriptor.handle == handle and descriptor.uuid == uuid: + return "Yes" + return "No" + + def MMI_IUT_DISCOVER_ALL_SERVICE_RECORD(self, pts_addr: bytes, description: str, **kwargs): + """ + Please send Service Discovery to discover all primary Services. Click + YES if GATT='XXXX'O services are discovered, otherwise click No. + Description: Verify that the Implementation Under Test (IUT) can + discover basic rate all primary services. + """ + + uuid = formatUuid(re.findall("'([a0-Z9]*)'O", description)) + self.services = self.gatt.DiscoverServicesSdp(address=pts_addr).service_uuids + if uuid in self.services: + return "Yes" + return "No" + + +common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb" + + +def stringHandleToInt(handle: str): + return int(handle, 16) + + +# Discovered characteristics handles are 1 more than PTS handles in one test. +def stringCharHandleToInt(handle: str): + return (int(handle, 16) + 1) + + +def formatUuid(uuid: str): + """ + Formats PTS described UUIDs to be of the right format. + Right format is: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' + PTS described format can be: + - 'XXXX' + - 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + - 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX' + """ + uuid_len = len(uuid) + if uuid_len == 4: + return common_uuid.replace(common_uuid[4:8], uuid.lower()) + elif uuid_len == 32 or uuid_len == 39: + uuidCharList = list(uuid.replace('-', '').lower()) + uuidCharList.insert(20, '-') + uuidCharList.insert(16, '-') + uuidCharList.insert(12, '-') + uuidCharList.insert(8, '-') + return ''.join(uuidCharList) + else: + return uuid + + +def compareIncludedServices(service, service_handle, included_handle, included_uuid): + """ + Compares included services with given values. + The service_handle passed by the PTS is + [primary service handle] + [included service number]. + """ + included_service_count = 1 + for included_service in service.included_services: + if service.handle == (service_handle - included_service_count)\ + and included_service.handle == included_handle\ + and included_service.uuid == included_uuid: + return True + included_service_count += 1 + return False + + +def getCharacteristicsForServiceUuid(services, uuid): + """ + Return an array of characteristics for matching service uuid. + """ + for service in services: + if service.uuid == uuid: + return service.characteristics + return [] + + +def getCharacteristicsRange(services, start_handle, end_handle, uuid): + """ + Return an array of characteristics of which handles are + between start_handle and end_handle and uuid matches. + """ + characteristics_list = [] + for service in services: + for characteristic in service.characteristics: + if characteristic.handle >= start_handle\ + and characteristic.handle <= end_handle\ + and characteristic.uuid == uuid: + characteristics_list.append(characteristic) + return characteristics_list + + +def getDescriptorsRange(services, start_handle, end_handle): + """ + Return an array of descriptors of which handles are + between start_handle and end_handle. + """ + descriptors_list = [] + for service in services: + for characteristic in service.characteristics: + for descriptor in characteristic.descriptors: + if descriptor.handle >= start_handle and descriptor.handle <= end_handle: + descriptors_list.append(descriptor) + return descriptors_list \ No newline at end of file diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml index 00b93b65a50..fb9b3066c26 100644 --- a/android/pandora/server/configs/PtsBotTest.xml +++ b/android/pandora/server/configs/PtsBotTest.xml @@ -23,6 +23,7 @@