Loading service/src/com/android/server/bluetooth/BluetoothAdapterState.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.STATE_OFF import kotlin.time.Duration import kotlin.time.toKotlinDuration import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull /** Thread safe class that allow waiting on a specific state change */ class BluetoothAdapterState { // MutableStateFlow cannot be used because it is conflated (See official doc) private val _uiState = MutableSharedFlow<Int>(1 /* replay only most recent value*/) init { assert(_uiState.tryEmit(STATE_OFF)) } fun set(s: Int) = runBlocking { _uiState.emit(s) } fun get(): Int = _uiState.replayCache.get(0) fun oneOf(vararg states: Int): Boolean = states.contains(get()) override fun toString() = BluetoothAdapter.nameForState(get()) fun waitForState(timeout: java.time.Duration, vararg states: Int) = runBlocking { waitForState(timeout.toKotlinDuration(), *states) } suspend fun waitForState(timeout: Duration, vararg states: Int): Boolean = withTimeoutOrNull(timeout) { _uiState.filter { states.contains(it) }.first() } != null } service/src/com/android/server/bluetooth/BluetoothManagerService.java +53 −78 Original line number Diff line number Diff line Loading @@ -104,7 +104,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; Loading Loading @@ -282,7 +281,8 @@ class BluetoothManagerService { private Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>(); private int mState = STATE_OFF; private final BluetoothAdapterState mState = new BluetoothAdapterState(); private final HandlerThread mBluetoothHandlerThread = BluetoothServerProxy.getInstance().createHandlerThread("BluetoothManagerService"); @VisibleForTesting private final BluetoothHandler mHandler; Loading Loading @@ -337,7 +337,7 @@ class BluetoothManagerService { if (state == STATE_BLE_TURNING_ON || state == STATE_TURNING_ON || state == STATE_TURNING_OFF) { if (!waitForState(Set.of(STATE_BLE_ON, STATE_ON))) { if (!waitForState(STATE_BLE_ON, STATE_ON)) { return false; } } Loading Loading @@ -647,9 +647,9 @@ class BluetoothManagerService { try { mEnable = false; mEnableExternal = false; if (mBluetooth != null && mState == STATE_BLE_ON) { if (mBluetooth != null && mState.oneOf(STATE_BLE_ON)) { synchronousOnBrEdrDown(mContext.getAttributionSource()); } else if (mBluetooth != null && mState == STATE_ON) { } else if (mBluetooth != null && mState.oneOf(STATE_ON)) { synchronousDisable(mContext.getAttributionSource()); } } catch (RemoteException | TimeoutException e) { Loading Loading @@ -1271,16 +1271,17 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } updateBleAppCount(token, true, packageName); if (mState == STATE_ON || mState == STATE_BLE_ON || mState == STATE_TURNING_ON || mState == STATE_TURNING_OFF || mState == STATE_BLE_TURNING_ON) { if (mState.oneOf( STATE_ON, STATE_BLE_ON, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON)) { Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on"); return true; } Loading @@ -1306,7 +1307,7 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } if (isSatelliteModeOn()) { Loading @@ -1314,13 +1315,13 @@ class BluetoothManagerService { return false; } if (mState == STATE_OFF) { if (mState.oneOf(STATE_OFF)) { Log.d(TAG, "disableBLE(): Already disabled"); return false; } updateBleAppCount(token, false, packageName); if (mState == STATE_BLE_ON && !isBleAppPresent()) { if (mState.oneOf(STATE_BLE_ON) && !isBleAppPresent()) { if (mEnable) { disableBleScanMode(); } Loading Loading @@ -1455,7 +1456,7 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } synchronized (mReceiver) { Loading Loading @@ -1579,7 +1580,7 @@ class BluetoothManagerService { boolean bindBluetoothProfileService( int bluetoothProfile, String serviceName, IBluetoothProfileServiceConnection proxy) { if (mState != STATE_ON) { if (!mState.oneOf(BluetoothAdapter.STATE_ON)) { if (DBG) { Log.d( TAG, Loading Loading @@ -2224,7 +2225,7 @@ class BluetoothManagerService { case MESSAGE_HANDLE_ENABLE_DELAYED: // The Bluetooth is turning off, wait for STATE_OFF if (mState != STATE_OFF) { if (!mState.oneOf(STATE_OFF)) { if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForEnableRetry++; Message enableDelayedMsg = Loading @@ -2248,7 +2249,7 @@ class BluetoothManagerService { Log.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); if (!disabling) { // The Bluetooth is turning on, wait for STATE_ON if (mState != STATE_ON) { if (!mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; Message disableDelayedMsg = Loading @@ -2272,7 +2273,7 @@ class BluetoothManagerService { mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); } else { // The Bluetooth is turning off, wait for exiting STATE_ON if (mState == STATE_ON) { if (mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; Message disableDelayedMsg = Loading Loading @@ -2413,16 +2414,15 @@ class BluetoothManagerService { } if (!mEnable) { waitForState(Set.of(STATE_ON)); waitForState(STATE_ON); handleDisable(); waitForState( Set.of( STATE_OFF, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON, STATE_BLE_ON, STATE_BLE_TURNING_OFF)); STATE_BLE_TURNING_OFF); } break; Loading @@ -2437,7 +2437,7 @@ class BluetoothManagerService { + " > " + BluetoothAdapter.nameForState(newState)); } mState = newState; mState.set(newState); bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF // unbind and rebind bluetooth service and enable bluetooth Loading @@ -2459,7 +2459,7 @@ class BluetoothManagerService { if (prevState == STATE_BLE_TURNING_OFF && newState == STATE_OFF) { if (mEnable) { Log.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting."); waitForState(Set.of(STATE_OFF)); waitForState(STATE_OFF); mHandler.sendEmptyMessageDelayed( MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); } Loading Loading @@ -2511,16 +2511,16 @@ class BluetoothManagerService { // Send BT state broadcast to update // the BT icon correctly if (mState == STATE_TURNING_ON || mState == STATE_ON) { if (mState.oneOf(STATE_TURNING_ON, STATE_ON)) { bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); mState = STATE_TURNING_OFF; mState.set(STATE_TURNING_OFF); } if (mState == STATE_TURNING_OFF) { if (mState.oneOf(STATE_TURNING_OFF)) { bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); break; case MESSAGE_RESTART_BLUETOOTH_SERVICE: Loading Loading @@ -2612,20 +2612,20 @@ class BluetoothManagerService { mBluetoothLock.readLock().unlock(); } if (mState == STATE_TURNING_OFF) { if (mState.oneOf(STATE_TURNING_OFF)) { // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE bluetoothStateChangeHandler(mState, STATE_OFF); mState = STATE_OFF; bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); mState.set(STATE_OFF); } if (mState == STATE_OFF) { bluetoothStateChangeHandler(mState, STATE_TURNING_ON); mState = STATE_TURNING_ON; if (mState.oneOf(STATE_OFF)) { bluetoothStateChangeHandler(STATE_OFF, STATE_TURNING_ON); mState.set(STATE_TURNING_ON); } waitForState(Set.of(STATE_ON)); waitForState(STATE_ON); if (mState == STATE_TURNING_ON) { bluetoothStateChangeHandler(mState, STATE_ON); if (mState.oneOf(STATE_TURNING_ON)) { bluetoothStateChangeHandler(STATE_TURNING_ON, STATE_ON); } unbindAllBluetoothProfileServices(); Loading @@ -2635,7 +2635,7 @@ class BluetoothManagerService { // Pbap service need receive STATE_TURNING_OFF intent to close bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); boolean didDisableTimeout = !waitForState(Set.of(STATE_OFF)); boolean didDisableTimeout = !waitForState(STATE_OFF); bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); sendBluetoothServiceDownCallback(); Loading Loading @@ -2664,7 +2664,7 @@ class BluetoothManagerService { } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); // enable addActiveLog(reason, mContext.getPackageName(), true); // mEnable flag could have been reset on disableBLE. Reenable it. Loading Loading @@ -2877,37 +2877,11 @@ class BluetoothManagerService { } boolean waitForManagerState(int state) { return waitForState(Set.of(state), false); return mState.waitForState(getSyncTimeout(), state); } private boolean waitForState(Set<Integer> states) { return waitForState(states, true); } private boolean waitForState(Set<Integer> states, boolean failIfUnbind) { for (int i = 0; i < 10; i++) { mBluetoothLock.readLock().lock(); try { if (mBluetooth == null && failIfUnbind) { Log.e(TAG, "waitForState " + states + " Bluetooth is not unbind"); return false; } if (mBluetooth == null && states.contains(STATE_OFF)) { return true; // We are so OFF that the bluetooth is not bind } if (mBluetooth != null && states.contains(synchronousGetState())) { return true; } } catch (RemoteException | TimeoutException e) { Log.e(TAG, "getState()", e); break; } finally { mBluetoothLock.readLock().unlock(); } SystemClock.sleep(300); } Log.e(TAG, "waitForState " + states + " time out"); return false; private boolean waitForState(int... states) { return mState.waitForState(getSyncTimeout(), states); } private void sendDisableMsg(int reason, String packageName) { Loading Loading @@ -2993,7 +2967,7 @@ class BluetoothManagerService { false); handleDisable(); waitForState(Set.of(STATE_OFF)); waitForState(STATE_OFF); sendBluetoothServiceDownCallback(); Loading @@ -3010,7 +2984,7 @@ class BluetoothManagerService { } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); if (clearBle) { clearBleApps(); Loading Loading @@ -3129,7 +3103,7 @@ class BluetoothManagerService { writer.println("Bluetooth Status"); writer.println(" enabled: " + isEnabled()); writer.println(" state: " + BluetoothAdapter.nameForState(mState)); writer.println(" state: " + mState); writer.println(" address: " + mAddress); writer.println(" name: " + mName); if (mEnable) { Loading Loading @@ -3203,9 +3177,10 @@ class BluetoothManagerService { private void dumpProto(FileDescriptor fd) { final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd)); proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled()); proto.write(BluetoothManagerServiceDumpProto.STATE, mState); proto.write(BluetoothManagerServiceDumpProto.STATE, mState.get()); proto.write( BluetoothManagerServiceDumpProto.STATE_NAME, BluetoothAdapter.nameForState(mState)); BluetoothManagerServiceDumpProto.STATE_NAME, BluetoothAdapter.nameForState(mState.get())); proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress); proto.write(BluetoothManagerServiceDumpProto.NAME, mName); if (mEnable) { Loading service/tests/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ android_test { srcs: [ "src/**/*.java", "src/**/*.kt", ], dxflags: ["--multi-dex"], Loading @@ -32,6 +33,7 @@ android_test { "androidx.test.rules", "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "kotlinx_coroutines_test", "platform-compat-test-rules", "platform-test-annotations", "service-bluetooth.change-ids", Loading service/tests/src/com/android/server/bluetooth/BluetoothAdapterStateTest.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.bluetooth.BluetoothAdapter.STATE_OFF import android.util.Log import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.days import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(JUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class BluetoothAdapterStateTest { lateinit var mState: BluetoothAdapterState @Before fun setUp() { mState = BluetoothAdapterState() } @Test fun testStateIsProperlyInit() { Log.d("BluetoothAdapterStateTest", "Initial state is " + mState) assertThat(mState.get()).isEqualTo(STATE_OFF) } @Test fun testStateReturnOnlyLastValue() { val max = 10 for (i in 0..max) mState.set(i) assertThat(mState.get()).isEqualTo(max) } @Test fun testStateDoesNotTimeoutWhenStateIsAlreadyCorrect() = runTest { val state = 10 mState.set(state) assertThat(runBlocking { mState.waitForState(100.days, state) }).isTrue() } @Test fun testStateTimeout() = runTest { assertThat(mState.waitForState(100.days, -1)).isFalse() } @Test fun testStateConcurrent() = runTest { val state = 42 val waiter = async { mState.waitForState(100.days, state) } mState.set(state) assertThat(waiter.await()).isTrue() } @Test fun testStateMultipleWaiters() = runTest { val state0 = 42 val state1 = 50 val state2 = 65 val waiter0 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state0) } val waiter1 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state1) } val waiter2 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state2) } val waiter3 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, -1) } mState.set(state0) yield() mState.set(state1) yield() mState.set(state2) assertThat(waiter0.await()).isTrue() assertThat(waiter1.await()).isTrue() assertThat(waiter2.await()).isTrue() assertThat(waiter3.await()).isFalse() } @Test fun testStateTimeoutFromJava() = runTest { assertThat(mState.waitForState(java.time.Duration.ofMillis(10), -1)).isFalse() } @Test fun testStateCycle() = runTest { val state0 = 42 val state1 = 50 mState.set(state0) yield() mState.set(state1) val waiter = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state0) } assertThat(waiter.await()).isFalse() } @Test fun testStateOneOf() { val state0 = 42 val state1 = 50 val state2 = 65 mState.set(state0) assertThat(mState.oneOf(state0, state1)).isTrue() assertThat(mState.oneOf(state1, state2)).isFalse() } } Loading
service/src/com/android/server/bluetooth/BluetoothAdapterState.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.STATE_OFF import kotlin.time.Duration import kotlin.time.toKotlinDuration import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull /** Thread safe class that allow waiting on a specific state change */ class BluetoothAdapterState { // MutableStateFlow cannot be used because it is conflated (See official doc) private val _uiState = MutableSharedFlow<Int>(1 /* replay only most recent value*/) init { assert(_uiState.tryEmit(STATE_OFF)) } fun set(s: Int) = runBlocking { _uiState.emit(s) } fun get(): Int = _uiState.replayCache.get(0) fun oneOf(vararg states: Int): Boolean = states.contains(get()) override fun toString() = BluetoothAdapter.nameForState(get()) fun waitForState(timeout: java.time.Duration, vararg states: Int) = runBlocking { waitForState(timeout.toKotlinDuration(), *states) } suspend fun waitForState(timeout: Duration, vararg states: Int): Boolean = withTimeoutOrNull(timeout) { _uiState.filter { states.contains(it) }.first() } != null }
service/src/com/android/server/bluetooth/BluetoothManagerService.java +53 −78 Original line number Diff line number Diff line Loading @@ -104,7 +104,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; Loading Loading @@ -282,7 +281,8 @@ class BluetoothManagerService { private Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>(); private int mState = STATE_OFF; private final BluetoothAdapterState mState = new BluetoothAdapterState(); private final HandlerThread mBluetoothHandlerThread = BluetoothServerProxy.getInstance().createHandlerThread("BluetoothManagerService"); @VisibleForTesting private final BluetoothHandler mHandler; Loading Loading @@ -337,7 +337,7 @@ class BluetoothManagerService { if (state == STATE_BLE_TURNING_ON || state == STATE_TURNING_ON || state == STATE_TURNING_OFF) { if (!waitForState(Set.of(STATE_BLE_ON, STATE_ON))) { if (!waitForState(STATE_BLE_ON, STATE_ON)) { return false; } } Loading Loading @@ -647,9 +647,9 @@ class BluetoothManagerService { try { mEnable = false; mEnableExternal = false; if (mBluetooth != null && mState == STATE_BLE_ON) { if (mBluetooth != null && mState.oneOf(STATE_BLE_ON)) { synchronousOnBrEdrDown(mContext.getAttributionSource()); } else if (mBluetooth != null && mState == STATE_ON) { } else if (mBluetooth != null && mState.oneOf(STATE_ON)) { synchronousDisable(mContext.getAttributionSource()); } } catch (RemoteException | TimeoutException e) { Loading Loading @@ -1271,16 +1271,17 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } updateBleAppCount(token, true, packageName); if (mState == STATE_ON || mState == STATE_BLE_ON || mState == STATE_TURNING_ON || mState == STATE_TURNING_OFF || mState == STATE_BLE_TURNING_ON) { if (mState.oneOf( STATE_ON, STATE_BLE_ON, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON)) { Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on"); return true; } Loading @@ -1306,7 +1307,7 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } if (isSatelliteModeOn()) { Loading @@ -1314,13 +1315,13 @@ class BluetoothManagerService { return false; } if (mState == STATE_OFF) { if (mState.oneOf(STATE_OFF)) { Log.d(TAG, "disableBLE(): Already disabled"); return false; } updateBleAppCount(token, false, packageName); if (mState == STATE_BLE_ON && !isBleAppPresent()) { if (mState.oneOf(STATE_BLE_ON) && !isBleAppPresent()) { if (mEnable) { disableBleScanMode(); } Loading Loading @@ -1455,7 +1456,7 @@ class BluetoothManagerService { + " mBinding=" + mBinding + " mState=" + BluetoothAdapter.nameForState(mState)); + mState); } synchronized (mReceiver) { Loading Loading @@ -1579,7 +1580,7 @@ class BluetoothManagerService { boolean bindBluetoothProfileService( int bluetoothProfile, String serviceName, IBluetoothProfileServiceConnection proxy) { if (mState != STATE_ON) { if (!mState.oneOf(BluetoothAdapter.STATE_ON)) { if (DBG) { Log.d( TAG, Loading Loading @@ -2224,7 +2225,7 @@ class BluetoothManagerService { case MESSAGE_HANDLE_ENABLE_DELAYED: // The Bluetooth is turning off, wait for STATE_OFF if (mState != STATE_OFF) { if (!mState.oneOf(STATE_OFF)) { if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForEnableRetry++; Message enableDelayedMsg = Loading @@ -2248,7 +2249,7 @@ class BluetoothManagerService { Log.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); if (!disabling) { // The Bluetooth is turning on, wait for STATE_ON if (mState != STATE_ON) { if (!mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; Message disableDelayedMsg = Loading @@ -2272,7 +2273,7 @@ class BluetoothManagerService { mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); } else { // The Bluetooth is turning off, wait for exiting STATE_ON if (mState == STATE_ON) { if (mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; Message disableDelayedMsg = Loading Loading @@ -2413,16 +2414,15 @@ class BluetoothManagerService { } if (!mEnable) { waitForState(Set.of(STATE_ON)); waitForState(STATE_ON); handleDisable(); waitForState( Set.of( STATE_OFF, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON, STATE_BLE_ON, STATE_BLE_TURNING_OFF)); STATE_BLE_TURNING_OFF); } break; Loading @@ -2437,7 +2437,7 @@ class BluetoothManagerService { + " > " + BluetoothAdapter.nameForState(newState)); } mState = newState; mState.set(newState); bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF // unbind and rebind bluetooth service and enable bluetooth Loading @@ -2459,7 +2459,7 @@ class BluetoothManagerService { if (prevState == STATE_BLE_TURNING_OFF && newState == STATE_OFF) { if (mEnable) { Log.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting."); waitForState(Set.of(STATE_OFF)); waitForState(STATE_OFF); mHandler.sendEmptyMessageDelayed( MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); } Loading Loading @@ -2511,16 +2511,16 @@ class BluetoothManagerService { // Send BT state broadcast to update // the BT icon correctly if (mState == STATE_TURNING_ON || mState == STATE_ON) { if (mState.oneOf(STATE_TURNING_ON, STATE_ON)) { bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); mState = STATE_TURNING_OFF; mState.set(STATE_TURNING_OFF); } if (mState == STATE_TURNING_OFF) { if (mState.oneOf(STATE_TURNING_OFF)) { bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); break; case MESSAGE_RESTART_BLUETOOTH_SERVICE: Loading Loading @@ -2612,20 +2612,20 @@ class BluetoothManagerService { mBluetoothLock.readLock().unlock(); } if (mState == STATE_TURNING_OFF) { if (mState.oneOf(STATE_TURNING_OFF)) { // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE bluetoothStateChangeHandler(mState, STATE_OFF); mState = STATE_OFF; bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); mState.set(STATE_OFF); } if (mState == STATE_OFF) { bluetoothStateChangeHandler(mState, STATE_TURNING_ON); mState = STATE_TURNING_ON; if (mState.oneOf(STATE_OFF)) { bluetoothStateChangeHandler(STATE_OFF, STATE_TURNING_ON); mState.set(STATE_TURNING_ON); } waitForState(Set.of(STATE_ON)); waitForState(STATE_ON); if (mState == STATE_TURNING_ON) { bluetoothStateChangeHandler(mState, STATE_ON); if (mState.oneOf(STATE_TURNING_ON)) { bluetoothStateChangeHandler(STATE_TURNING_ON, STATE_ON); } unbindAllBluetoothProfileServices(); Loading @@ -2635,7 +2635,7 @@ class BluetoothManagerService { // Pbap service need receive STATE_TURNING_OFF intent to close bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); boolean didDisableTimeout = !waitForState(Set.of(STATE_OFF)); boolean didDisableTimeout = !waitForState(STATE_OFF); bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); sendBluetoothServiceDownCallback(); Loading Loading @@ -2664,7 +2664,7 @@ class BluetoothManagerService { } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); // enable addActiveLog(reason, mContext.getPackageName(), true); // mEnable flag could have been reset on disableBLE. Reenable it. Loading Loading @@ -2877,37 +2877,11 @@ class BluetoothManagerService { } boolean waitForManagerState(int state) { return waitForState(Set.of(state), false); return mState.waitForState(getSyncTimeout(), state); } private boolean waitForState(Set<Integer> states) { return waitForState(states, true); } private boolean waitForState(Set<Integer> states, boolean failIfUnbind) { for (int i = 0; i < 10; i++) { mBluetoothLock.readLock().lock(); try { if (mBluetooth == null && failIfUnbind) { Log.e(TAG, "waitForState " + states + " Bluetooth is not unbind"); return false; } if (mBluetooth == null && states.contains(STATE_OFF)) { return true; // We are so OFF that the bluetooth is not bind } if (mBluetooth != null && states.contains(synchronousGetState())) { return true; } } catch (RemoteException | TimeoutException e) { Log.e(TAG, "getState()", e); break; } finally { mBluetoothLock.readLock().unlock(); } SystemClock.sleep(300); } Log.e(TAG, "waitForState " + states + " time out"); return false; private boolean waitForState(int... states) { return mState.waitForState(getSyncTimeout(), states); } private void sendDisableMsg(int reason, String packageName) { Loading Loading @@ -2993,7 +2967,7 @@ class BluetoothManagerService { false); handleDisable(); waitForState(Set.of(STATE_OFF)); waitForState(STATE_OFF); sendBluetoothServiceDownCallback(); Loading @@ -3010,7 +2984,7 @@ class BluetoothManagerService { } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = STATE_OFF; mState.set(STATE_OFF); if (clearBle) { clearBleApps(); Loading Loading @@ -3129,7 +3103,7 @@ class BluetoothManagerService { writer.println("Bluetooth Status"); writer.println(" enabled: " + isEnabled()); writer.println(" state: " + BluetoothAdapter.nameForState(mState)); writer.println(" state: " + mState); writer.println(" address: " + mAddress); writer.println(" name: " + mName); if (mEnable) { Loading Loading @@ -3203,9 +3177,10 @@ class BluetoothManagerService { private void dumpProto(FileDescriptor fd) { final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd)); proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled()); proto.write(BluetoothManagerServiceDumpProto.STATE, mState); proto.write(BluetoothManagerServiceDumpProto.STATE, mState.get()); proto.write( BluetoothManagerServiceDumpProto.STATE_NAME, BluetoothAdapter.nameForState(mState)); BluetoothManagerServiceDumpProto.STATE_NAME, BluetoothAdapter.nameForState(mState.get())); proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress); proto.write(BluetoothManagerServiceDumpProto.NAME, mName); if (mEnable) { Loading
service/tests/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ android_test { srcs: [ "src/**/*.java", "src/**/*.kt", ], dxflags: ["--multi-dex"], Loading @@ -32,6 +33,7 @@ android_test { "androidx.test.rules", "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "kotlinx_coroutines_test", "platform-compat-test-rules", "platform-test-annotations", "service-bluetooth.change-ids", Loading
service/tests/src/com/android/server/bluetooth/BluetoothAdapterStateTest.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.bluetooth.BluetoothAdapter.STATE_OFF import android.util.Log import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.days import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(JUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class BluetoothAdapterStateTest { lateinit var mState: BluetoothAdapterState @Before fun setUp() { mState = BluetoothAdapterState() } @Test fun testStateIsProperlyInit() { Log.d("BluetoothAdapterStateTest", "Initial state is " + mState) assertThat(mState.get()).isEqualTo(STATE_OFF) } @Test fun testStateReturnOnlyLastValue() { val max = 10 for (i in 0..max) mState.set(i) assertThat(mState.get()).isEqualTo(max) } @Test fun testStateDoesNotTimeoutWhenStateIsAlreadyCorrect() = runTest { val state = 10 mState.set(state) assertThat(runBlocking { mState.waitForState(100.days, state) }).isTrue() } @Test fun testStateTimeout() = runTest { assertThat(mState.waitForState(100.days, -1)).isFalse() } @Test fun testStateConcurrent() = runTest { val state = 42 val waiter = async { mState.waitForState(100.days, state) } mState.set(state) assertThat(waiter.await()).isTrue() } @Test fun testStateMultipleWaiters() = runTest { val state0 = 42 val state1 = 50 val state2 = 65 val waiter0 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state0) } val waiter1 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state1) } val waiter2 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state2) } val waiter3 = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, -1) } mState.set(state0) yield() mState.set(state1) yield() mState.set(state2) assertThat(waiter0.await()).isTrue() assertThat(waiter1.await()).isTrue() assertThat(waiter2.await()).isTrue() assertThat(waiter3.await()).isFalse() } @Test fun testStateTimeoutFromJava() = runTest { assertThat(mState.waitForState(java.time.Duration.ofMillis(10), -1)).isFalse() } @Test fun testStateCycle() = runTest { val state0 = 42 val state1 = 50 mState.set(state0) yield() mState.set(state1) val waiter = async(start = CoroutineStart.UNDISPATCHED) { mState.waitForState(100.days, state0) } assertThat(waiter.await()).isFalse() } @Test fun testStateOneOf() { val state0 = 42 val state1 = 50 val state2 = 65 mState.set(state0) assertThat(mState.oneOf(state0, state1)).isTrue() assertThat(mState.oneOf(state1, state2)).isFalse() } }