Loading android/app/AndroidManifest.xml +1 −4 Original line number Diff line number Diff line Loading @@ -519,12 +519,9 @@ <action android:name="android.bluetooth.IBluetoothPbapClient"/> </intent-filter> </service> <!-- Note: This service doesn't get started, it just indicates to the Authentication framework that we can create accounts of a specific type. As such, its safe to have as enabled on all targets and not just the ones that use PBAP Client --> <service android:process="@string/process" android:name="com.android.bluetooth.pbapclient.AuthenticationService" android:enabled="true" android:enabled="false" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +45 −6 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; Loading @@ -68,6 +69,7 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); private final Object mSearchScanCallbackLock = new Object(); private final Map<Integer, ScanResult> mScanBroadcasts = new HashMap<>(); private HandlerThread mStateMachinesThread; private HandlerThread mCallbackHandlerThread; Loading Loading @@ -325,8 +327,16 @@ public class BassClientService extends ProfileService { } private boolean hasRoomForBroadcastSourceAddition(BluetoothDevice device) { List<BluetoothLeBroadcastReceiveState> currentAllSources = getAllSources(device); return currentAllSources.size() < getMaximumSourceCapacity(device); boolean isRoomAvailable = false; String emptyBluetoothDevice = "00:00:00:00:00:00"; for (BluetoothLeBroadcastReceiveState recvState: getAllSources(device)) { if (recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) { isRoomAvailable = true; break; } } log("isRoomAvailable: " + isRoomAvailable); return isRoomAvailable; } private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { Loading Loading @@ -632,16 +642,29 @@ public class BassClientService extends ProfileService { BassConstants.BAAS_UUID)) { return; } Message msg = mBassUtils.getAutoAssistScanHandler() .obtainMessage(BassConstants.AA_SCAN_SUCCESS); msg.obj = result; mBassUtils.getAutoAssistScanHandler().sendMessage(msg); log( "Broadcast Source Found:" + result.getDevice()); byte[] broadcastIdArray = listOfUuids.get(BassConstants.BAAS_UUID); int broadcastId = (int)(((broadcastIdArray[2] & 0xff) << 16) | ((broadcastIdArray[1] & 0xff) << 8) | (broadcastIdArray[0] & 0xff)); if (mScanBroadcasts.get(broadcastId) == null) { log("selectBroadcastSource: broadcastId " + broadcastId); mScanBroadcasts.put(broadcastId, result); synchronized (mStateMachines) { for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.isConnected()) { selectSource(sm.getDevice(), result, false); } } } } } public void onScanFailed(int errorCode) { Log.e(TAG, "Scan Failure:" + errorCode); } }; mScanBroadcasts.clear(); ScanSettings settings = new ScanSettings.Builder().setCallbackType( ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) Loading Loading @@ -687,6 +710,7 @@ public class BassClientService extends ProfileService { scanner.stopScan(mSearchScanCallback); mSearchScanCallback = null; mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); mScanBroadcasts.clear(); } } Loading Loading @@ -792,6 +816,7 @@ public class BassClientService extends ProfileService { } Message message = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); message.arg1 = sourceId; message.arg2 = BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID; message.obj = updatedMetadata; stateMachine.sendMessage(message); } Loading Loading @@ -820,6 +845,20 @@ public class BassClientService extends ProfileService { BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR); return; } BluetoothLeBroadcastReceiveState recvState = stateMachine.getBroadcastReceiveStateForSourceId(sourceId); BluetoothLeBroadcastMetadata metaData = stateMachine.getCurrentBroadcastMetadata(sourceId); if (metaData != null && recvState != null && recvState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { log("Force source to lost PA sync"); Message message = stateMachine.obtainMessage( BassClientStateMachine.UPDATE_BCAST_SOURCE); message.arg1 = sourceId; message.arg2 = BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE; message.obj = metaData; stateMachine.sendMessage(message); } Message message = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); message.arg1 = sourceId; stateMachine.sendMessage(message); Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +36 −12 Original line number Diff line number Diff line Loading @@ -436,11 +436,13 @@ public class BassClientStateMachine extends StateMachine { channel.setSelected(false); subGroup.addChannel(channel.build()); } subGroup.setCodecId((long)(baseLevel2.codecId[4] << 32 | baseLevel2.codecId[3] << 24 | baseLevel2.codecId[2] << 16 | baseLevel2.codecId[1] << 8 | baseLevel2.codecId[0])); byte[] arrayCodecId = baseLevel2.codecId; long codeId = (long) ((arrayCodecId[4] & 0xff) << 32 | (arrayCodecId[3] & 0xff) << 24 | (arrayCodecId[2] & 0xff) << 16 | (arrayCodecId[1] & 0xff) << 8 | (arrayCodecId[0] & 0xff)); subGroup.setCodecId(codeId); subGroup.setCodecSpecificConfig(BluetoothLeAudioCodecConfigMetadata. fromRawBytes(baseLevel2.codecConfigInfo)); subGroup.setContentMetadata(BluetoothLeAudioContentMetadata. Loading @@ -448,6 +450,18 @@ public class BassClientStateMachine extends StateMachine { metaData.addSubgroup(subGroup.build()); } metaData.setSourceDevice(device, device.getAddressType()); byte[] arrayPresentationDelay = baseData.getLevelOne().presentationDelay; int presentationDelay = (int) ((arrayPresentationDelay[2] & 0xff) << 16 | (arrayPresentationDelay[1] & 0xff) | (arrayPresentationDelay[0] & 0xff)); metaData.setPresentationDelayMicros(presentationDelay); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult(device); if (result != null) { int broadcastId = result.getBroadcastId(); log("broadcast ID: " + broadcastId); metaData.setBroadcastId(broadcastId); } return metaData.build(); } Loading Loading @@ -638,6 +652,7 @@ public class BassClientStateMachine extends StateMachine { byte metaDataSyncState = receiverState[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX]; byte encryptionStatus = receiverState[BassConstants.BCAST_RCVR_STATE_ENC_STATUS_IDX]; byte[] badBroadcastCode = null; int badBroadcastCodeLen = 0; if (encryptionStatus == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) { badBroadcastCode = new byte[BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE]; Loading @@ -648,11 +663,12 @@ public class BassClientStateMachine extends StateMachine { 0, BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE); badBroadcastCode = reverseBytes(badBroadcastCode); badBroadcastCodeLen = BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE; } byte numSubGroups = receiverState[BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE]; + badBroadcastCodeLen]; int offset = BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE + 1; + badBroadcastCodeLen + 1; ArrayList<BluetoothLeAudioContentMetadata> metadataList = new ArrayList<BluetoothLeAudioContentMetadata>(); ArrayList<Long> audioSyncState = new ArrayList<Long>(); Loading @@ -664,7 +680,7 @@ public class BassClientStateMachine extends StateMachine { log("BIS index byte array: "); BassUtils.printByteArray(audioSyncIndex); ByteBuffer wrapped = ByteBuffer.wrap(reverseBytes(audioSyncIndex)); audioSyncState.add(wrapped.getLong()); audioSyncState.add((long) wrapped.getInt()); byte metaDataLength = receiverState[offset++]; if (metaDataLength > 0) { Loading Loading @@ -1255,7 +1271,7 @@ public class BassClientStateMachine extends StateMachine { } private byte[] convertBroadcastMetadataToUpdateSourceByteArray(int sourceId, BluetoothLeBroadcastMetadata metaData) { BluetoothLeBroadcastMetadata metaData, int paSync) { BluetoothLeBroadcastReceiveState existingState = getBroadcastReceiveStateForSourceId(sourceId); if (existingState == null) { Loading Loading @@ -1287,7 +1303,9 @@ public class BassClientStateMachine extends StateMachine { // Source_ID res[offset++] = (byte) sourceId; // PA_Sync if (existingState.getPaSyncState() if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) { res[offset++] = (byte) paSync; } else if (existingState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { res[offset++] = (byte) (0x01); } else { Loading @@ -1299,7 +1317,12 @@ public class BassClientStateMachine extends StateMachine { // Num_Subgroups res[offset++] = numSubGroups; for (int i = 0; i < numSubGroups; i++) { int bisIndexValue = existingState.getBisSyncState().get(i).intValue(); int bisIndexValue; if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) { bisIndexValue = 0; } else { bisIndexValue = existingState.getBisSyncState().get(i).intValue(); } log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); // BIS_Sync res[offset++] = (byte) (bisIndexValue & 0x00000000000000FF); Loading Loading @@ -1497,9 +1520,10 @@ public class BassClientStateMachine extends StateMachine { case UPDATE_BCAST_SOURCE: metaData = (BluetoothLeBroadcastMetadata) message.obj; int sourceId = message.arg1; int paSync = message.arg2; log("Updating Broadcast source" + metaData); byte[] updateSourceInfo = convertBroadcastMetadataToUpdateSourceByteArray( sourceId, metaData); sourceId, metaData, paSync); if (updateSourceInfo == null) { Log.e(TAG, "update source: source Info is NULL"); break; Loading android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java +86 −3 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.provider.CallLog; import android.sysprop.BluetoothProperties; import android.util.Log; Loading Loading @@ -60,6 +61,12 @@ public class PbapClientService extends ProfileService { private static final String TAG = "PbapClientService"; private static final String SERVICE_NAME = "Phonebook Access PCE"; /** * The component names for the owned authenticator service */ private static final String AUTHENTICATOR_SERVICE = AuthenticationService.class.getCanonicalName(); // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = Loading @@ -70,6 +77,40 @@ public class PbapClientService extends ProfileService { private DatabaseManager mDatabaseManager; /** * There's an ~1-2 second latency between when our Authentication service is set as available to * the system and when the Authentication/Account framework code will recognize it and allow us * to alter accounts. In lieu of the Accounts team dealing with this race condition, we're going * to periodically poll over 3 seconds until our accounts are visible, remove old accounts, and * then notify device state machines that they can create accounts and download contacts. */ // TODO(233361365): Remove this pattern when the framework solves their race condition private static final int ACCOUNT_VISIBILITY_CHECK_MS = 500; private static final int ACCOUNT_VISIBILITY_CHECK_TRIES_MAX = 6; private int mAccountVisibilityCheckTries = 0; private final Handler mAuthServiceHandler = new Handler(); private final Runnable mCheckAuthService = new Runnable() { @Override public void run() { // If our accounts are finally visible to use, clean up old ones and tell devices they // can issue downloads if they're ready. Otherwise, wait and try again. if (isAuthenticationServiceReady()) { Log.i(TAG, "Service ready! Clean up old accounts and try contacts downloads"); removeUncleanAccounts(); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.tryDownloadIfConnected(); } } else if (mAccountVisibilityCheckTries < ACCOUNT_VISIBILITY_CHECK_TRIES_MAX) { mAccountVisibilityCheckTries += 1; Log.w(TAG, "AccountManager hasn't registered our service yet. Retry " + mAccountVisibilityCheckTries + "/" + ACCOUNT_VISIBILITY_CHECK_TRIES_MAX); mAuthServiceHandler.postDelayed(this, ACCOUNT_VISIBILITY_CHECK_MS); } else { Log.e(TAG, "Failed to register Authenication Service and get account visibility"); } } }; public static boolean isEnabled() { return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); } Loading @@ -88,6 +129,8 @@ public class PbapClientService extends ProfileService { mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), "DatabaseManager cannot be null when PbapClientService starts"); setComponentAvailable(AUTHENTICATOR_SERVICE, true); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // delay initial download until after the user is unlocked to add an account. Loading @@ -101,7 +144,7 @@ public class PbapClientService extends ProfileService { Log.w(TAG, "Unable to register pbapclient receiver", e); } removeUncleanAccounts(); initializeAuthenticationService(); registerSdpRecord(); setPbapClientService(this); return true; Loading @@ -119,7 +162,8 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } removeUncleanAccounts(); cleanupAuthenicationService(); setComponentAvailable(AUTHENTICATOR_SERVICE, false); return true; } Loading @@ -133,7 +177,45 @@ public class PbapClientService extends ProfileService { } } /** * Periodically check if the account framework has recognized our service and will allow us to * interact with our accounts. Notify state machines once our service is ready so we can trigger * account downloads. */ private void initializeAuthenticationService() { mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); } private void cleanupAuthenicationService() { mAuthServiceHandler.removeCallbacks(mCheckAuthService); removeUncleanAccounts(); } /** * Determine if our account type is visible to us yet. If it is, then our service is ready and * our account type is ready to use. * * Make a placeholder device account and determine our visibility relative to it. Note that this * function uses the same restrictions are the other add and remove functions, but is *also* * available to all system apps instead of throwing a runtime SecurityException. */ protected boolean isAuthenticationServiceReady() { Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); AccountManager accountManager = AccountManager.get(this); int visibility = accountManager.getAccountVisibility(account, getPackageName()); if (DBG) { Log.d(TAG, "Checking visibility, visibility=" + visibility); } return visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; } private void removeUncleanAccounts() { if (!isAuthenticationServiceReady()) { Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); return; } // Find all accounts that match the type "pbap" and delete them. AccountManager accountManager = AccountManager.get(this); Account[] accounts = Loading Loading @@ -208,7 +290,7 @@ public class PbapClientService extends ProfileService { } } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); stateMachine.tryDownloadIfConnected(); } } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. Loading Loading @@ -542,6 +624,7 @@ public class PbapClientService extends ProfileService { @Override public void dump(StringBuilder sb) { super.dump(sb); ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.dump(sb); } Loading android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +18 −7 Original line number Diff line number Diff line Loading @@ -301,10 +301,7 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTED); mMostRecentState = BluetoothProfile.STATE_CONNECTED; if (mUserManager.isUserUnlocked()) { mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } downloadIfReady(); } @Override Loading @@ -321,8 +318,7 @@ final class PbapClientStateMachine extends StateMachine { break; case MSG_RESUME_DOWNLOAD: mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); downloadIfReady(); break; default: Loading @@ -333,6 +329,21 @@ final class PbapClientStateMachine extends StateMachine { } } /** * Trigger a contacts download if the user is unlocked and our accounts are available to us */ private void downloadIfReady() { boolean userReady = mUserManager.isUserUnlocked(); boolean accountServiceReady = mService.isAuthenticationServiceReady(); if (!userReady || !accountServiceReady) { Log.w(TAG, "Cannot download contacts yet, userReady=" + userReady + ", accountServiceReady=" + accountServiceReady); return; } mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { if (device == null) { Log.w(TAG, "onConnectionStateChanged with invalid device"); Loading @@ -357,7 +368,7 @@ final class PbapClientStateMachine extends StateMachine { sendMessage(MSG_DISCONNECT, device); } public void resumeDownload() { public void tryDownloadIfConnected() { sendMessage(MSG_RESUME_DOWNLOAD); } Loading Loading
android/app/AndroidManifest.xml +1 −4 Original line number Diff line number Diff line Loading @@ -519,12 +519,9 @@ <action android:name="android.bluetooth.IBluetoothPbapClient"/> </intent-filter> </service> <!-- Note: This service doesn't get started, it just indicates to the Authentication framework that we can create accounts of a specific type. As such, its safe to have as enabled on all targets and not just the ones that use PBAP Client --> <service android:process="@string/process" android:name="com.android.bluetooth.pbapclient.AuthenticationService" android:enabled="true" android:enabled="false" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +45 −6 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; Loading @@ -68,6 +69,7 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); private final Object mSearchScanCallbackLock = new Object(); private final Map<Integer, ScanResult> mScanBroadcasts = new HashMap<>(); private HandlerThread mStateMachinesThread; private HandlerThread mCallbackHandlerThread; Loading Loading @@ -325,8 +327,16 @@ public class BassClientService extends ProfileService { } private boolean hasRoomForBroadcastSourceAddition(BluetoothDevice device) { List<BluetoothLeBroadcastReceiveState> currentAllSources = getAllSources(device); return currentAllSources.size() < getMaximumSourceCapacity(device); boolean isRoomAvailable = false; String emptyBluetoothDevice = "00:00:00:00:00:00"; for (BluetoothLeBroadcastReceiveState recvState: getAllSources(device)) { if (recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) { isRoomAvailable = true; break; } } log("isRoomAvailable: " + isRoomAvailable); return isRoomAvailable; } private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { Loading Loading @@ -632,16 +642,29 @@ public class BassClientService extends ProfileService { BassConstants.BAAS_UUID)) { return; } Message msg = mBassUtils.getAutoAssistScanHandler() .obtainMessage(BassConstants.AA_SCAN_SUCCESS); msg.obj = result; mBassUtils.getAutoAssistScanHandler().sendMessage(msg); log( "Broadcast Source Found:" + result.getDevice()); byte[] broadcastIdArray = listOfUuids.get(BassConstants.BAAS_UUID); int broadcastId = (int)(((broadcastIdArray[2] & 0xff) << 16) | ((broadcastIdArray[1] & 0xff) << 8) | (broadcastIdArray[0] & 0xff)); if (mScanBroadcasts.get(broadcastId) == null) { log("selectBroadcastSource: broadcastId " + broadcastId); mScanBroadcasts.put(broadcastId, result); synchronized (mStateMachines) { for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.isConnected()) { selectSource(sm.getDevice(), result, false); } } } } } public void onScanFailed(int errorCode) { Log.e(TAG, "Scan Failure:" + errorCode); } }; mScanBroadcasts.clear(); ScanSettings settings = new ScanSettings.Builder().setCallbackType( ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) Loading Loading @@ -687,6 +710,7 @@ public class BassClientService extends ProfileService { scanner.stopScan(mSearchScanCallback); mSearchScanCallback = null; mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); mScanBroadcasts.clear(); } } Loading Loading @@ -792,6 +816,7 @@ public class BassClientService extends ProfileService { } Message message = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); message.arg1 = sourceId; message.arg2 = BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID; message.obj = updatedMetadata; stateMachine.sendMessage(message); } Loading Loading @@ -820,6 +845,20 @@ public class BassClientService extends ProfileService { BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR); return; } BluetoothLeBroadcastReceiveState recvState = stateMachine.getBroadcastReceiveStateForSourceId(sourceId); BluetoothLeBroadcastMetadata metaData = stateMachine.getCurrentBroadcastMetadata(sourceId); if (metaData != null && recvState != null && recvState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { log("Force source to lost PA sync"); Message message = stateMachine.obtainMessage( BassClientStateMachine.UPDATE_BCAST_SOURCE); message.arg1 = sourceId; message.arg2 = BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE; message.obj = metaData; stateMachine.sendMessage(message); } Message message = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); message.arg1 = sourceId; stateMachine.sendMessage(message); Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +36 −12 Original line number Diff line number Diff line Loading @@ -436,11 +436,13 @@ public class BassClientStateMachine extends StateMachine { channel.setSelected(false); subGroup.addChannel(channel.build()); } subGroup.setCodecId((long)(baseLevel2.codecId[4] << 32 | baseLevel2.codecId[3] << 24 | baseLevel2.codecId[2] << 16 | baseLevel2.codecId[1] << 8 | baseLevel2.codecId[0])); byte[] arrayCodecId = baseLevel2.codecId; long codeId = (long) ((arrayCodecId[4] & 0xff) << 32 | (arrayCodecId[3] & 0xff) << 24 | (arrayCodecId[2] & 0xff) << 16 | (arrayCodecId[1] & 0xff) << 8 | (arrayCodecId[0] & 0xff)); subGroup.setCodecId(codeId); subGroup.setCodecSpecificConfig(BluetoothLeAudioCodecConfigMetadata. fromRawBytes(baseLevel2.codecConfigInfo)); subGroup.setContentMetadata(BluetoothLeAudioContentMetadata. Loading @@ -448,6 +450,18 @@ public class BassClientStateMachine extends StateMachine { metaData.addSubgroup(subGroup.build()); } metaData.setSourceDevice(device, device.getAddressType()); byte[] arrayPresentationDelay = baseData.getLevelOne().presentationDelay; int presentationDelay = (int) ((arrayPresentationDelay[2] & 0xff) << 16 | (arrayPresentationDelay[1] & 0xff) | (arrayPresentationDelay[0] & 0xff)); metaData.setPresentationDelayMicros(presentationDelay); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult(device); if (result != null) { int broadcastId = result.getBroadcastId(); log("broadcast ID: " + broadcastId); metaData.setBroadcastId(broadcastId); } return metaData.build(); } Loading Loading @@ -638,6 +652,7 @@ public class BassClientStateMachine extends StateMachine { byte metaDataSyncState = receiverState[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX]; byte encryptionStatus = receiverState[BassConstants.BCAST_RCVR_STATE_ENC_STATUS_IDX]; byte[] badBroadcastCode = null; int badBroadcastCodeLen = 0; if (encryptionStatus == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) { badBroadcastCode = new byte[BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE]; Loading @@ -648,11 +663,12 @@ public class BassClientStateMachine extends StateMachine { 0, BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE); badBroadcastCode = reverseBytes(badBroadcastCode); badBroadcastCodeLen = BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE; } byte numSubGroups = receiverState[BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE]; + badBroadcastCodeLen]; int offset = BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE + 1; + badBroadcastCodeLen + 1; ArrayList<BluetoothLeAudioContentMetadata> metadataList = new ArrayList<BluetoothLeAudioContentMetadata>(); ArrayList<Long> audioSyncState = new ArrayList<Long>(); Loading @@ -664,7 +680,7 @@ public class BassClientStateMachine extends StateMachine { log("BIS index byte array: "); BassUtils.printByteArray(audioSyncIndex); ByteBuffer wrapped = ByteBuffer.wrap(reverseBytes(audioSyncIndex)); audioSyncState.add(wrapped.getLong()); audioSyncState.add((long) wrapped.getInt()); byte metaDataLength = receiverState[offset++]; if (metaDataLength > 0) { Loading Loading @@ -1255,7 +1271,7 @@ public class BassClientStateMachine extends StateMachine { } private byte[] convertBroadcastMetadataToUpdateSourceByteArray(int sourceId, BluetoothLeBroadcastMetadata metaData) { BluetoothLeBroadcastMetadata metaData, int paSync) { BluetoothLeBroadcastReceiveState existingState = getBroadcastReceiveStateForSourceId(sourceId); if (existingState == null) { Loading Loading @@ -1287,7 +1303,9 @@ public class BassClientStateMachine extends StateMachine { // Source_ID res[offset++] = (byte) sourceId; // PA_Sync if (existingState.getPaSyncState() if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) { res[offset++] = (byte) paSync; } else if (existingState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { res[offset++] = (byte) (0x01); } else { Loading @@ -1299,7 +1317,12 @@ public class BassClientStateMachine extends StateMachine { // Num_Subgroups res[offset++] = numSubGroups; for (int i = 0; i < numSubGroups; i++) { int bisIndexValue = existingState.getBisSyncState().get(i).intValue(); int bisIndexValue; if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) { bisIndexValue = 0; } else { bisIndexValue = existingState.getBisSyncState().get(i).intValue(); } log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); // BIS_Sync res[offset++] = (byte) (bisIndexValue & 0x00000000000000FF); Loading Loading @@ -1497,9 +1520,10 @@ public class BassClientStateMachine extends StateMachine { case UPDATE_BCAST_SOURCE: metaData = (BluetoothLeBroadcastMetadata) message.obj; int sourceId = message.arg1; int paSync = message.arg2; log("Updating Broadcast source" + metaData); byte[] updateSourceInfo = convertBroadcastMetadataToUpdateSourceByteArray( sourceId, metaData); sourceId, metaData, paSync); if (updateSourceInfo == null) { Log.e(TAG, "update source: source Info is NULL"); break; Loading
android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java +86 −3 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.provider.CallLog; import android.sysprop.BluetoothProperties; import android.util.Log; Loading Loading @@ -60,6 +61,12 @@ public class PbapClientService extends ProfileService { private static final String TAG = "PbapClientService"; private static final String SERVICE_NAME = "Phonebook Access PCE"; /** * The component names for the owned authenticator service */ private static final String AUTHENTICATOR_SERVICE = AuthenticationService.class.getCanonicalName(); // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = Loading @@ -70,6 +77,40 @@ public class PbapClientService extends ProfileService { private DatabaseManager mDatabaseManager; /** * There's an ~1-2 second latency between when our Authentication service is set as available to * the system and when the Authentication/Account framework code will recognize it and allow us * to alter accounts. In lieu of the Accounts team dealing with this race condition, we're going * to periodically poll over 3 seconds until our accounts are visible, remove old accounts, and * then notify device state machines that they can create accounts and download contacts. */ // TODO(233361365): Remove this pattern when the framework solves their race condition private static final int ACCOUNT_VISIBILITY_CHECK_MS = 500; private static final int ACCOUNT_VISIBILITY_CHECK_TRIES_MAX = 6; private int mAccountVisibilityCheckTries = 0; private final Handler mAuthServiceHandler = new Handler(); private final Runnable mCheckAuthService = new Runnable() { @Override public void run() { // If our accounts are finally visible to use, clean up old ones and tell devices they // can issue downloads if they're ready. Otherwise, wait and try again. if (isAuthenticationServiceReady()) { Log.i(TAG, "Service ready! Clean up old accounts and try contacts downloads"); removeUncleanAccounts(); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.tryDownloadIfConnected(); } } else if (mAccountVisibilityCheckTries < ACCOUNT_VISIBILITY_CHECK_TRIES_MAX) { mAccountVisibilityCheckTries += 1; Log.w(TAG, "AccountManager hasn't registered our service yet. Retry " + mAccountVisibilityCheckTries + "/" + ACCOUNT_VISIBILITY_CHECK_TRIES_MAX); mAuthServiceHandler.postDelayed(this, ACCOUNT_VISIBILITY_CHECK_MS); } else { Log.e(TAG, "Failed to register Authenication Service and get account visibility"); } } }; public static boolean isEnabled() { return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); } Loading @@ -88,6 +129,8 @@ public class PbapClientService extends ProfileService { mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), "DatabaseManager cannot be null when PbapClientService starts"); setComponentAvailable(AUTHENTICATOR_SERVICE, true); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // delay initial download until after the user is unlocked to add an account. Loading @@ -101,7 +144,7 @@ public class PbapClientService extends ProfileService { Log.w(TAG, "Unable to register pbapclient receiver", e); } removeUncleanAccounts(); initializeAuthenticationService(); registerSdpRecord(); setPbapClientService(this); return true; Loading @@ -119,7 +162,8 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } removeUncleanAccounts(); cleanupAuthenicationService(); setComponentAvailable(AUTHENTICATOR_SERVICE, false); return true; } Loading @@ -133,7 +177,45 @@ public class PbapClientService extends ProfileService { } } /** * Periodically check if the account framework has recognized our service and will allow us to * interact with our accounts. Notify state machines once our service is ready so we can trigger * account downloads. */ private void initializeAuthenticationService() { mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); } private void cleanupAuthenicationService() { mAuthServiceHandler.removeCallbacks(mCheckAuthService); removeUncleanAccounts(); } /** * Determine if our account type is visible to us yet. If it is, then our service is ready and * our account type is ready to use. * * Make a placeholder device account and determine our visibility relative to it. Note that this * function uses the same restrictions are the other add and remove functions, but is *also* * available to all system apps instead of throwing a runtime SecurityException. */ protected boolean isAuthenticationServiceReady() { Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); AccountManager accountManager = AccountManager.get(this); int visibility = accountManager.getAccountVisibility(account, getPackageName()); if (DBG) { Log.d(TAG, "Checking visibility, visibility=" + visibility); } return visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; } private void removeUncleanAccounts() { if (!isAuthenticationServiceReady()) { Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); return; } // Find all accounts that match the type "pbap" and delete them. AccountManager accountManager = AccountManager.get(this); Account[] accounts = Loading Loading @@ -208,7 +290,7 @@ public class PbapClientService extends ProfileService { } } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); stateMachine.tryDownloadIfConnected(); } } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. Loading Loading @@ -542,6 +624,7 @@ public class PbapClientService extends ProfileService { @Override public void dump(StringBuilder sb) { super.dump(sb); ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.dump(sb); } Loading
android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +18 −7 Original line number Diff line number Diff line Loading @@ -301,10 +301,7 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTED); mMostRecentState = BluetoothProfile.STATE_CONNECTED; if (mUserManager.isUserUnlocked()) { mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } downloadIfReady(); } @Override Loading @@ -321,8 +318,7 @@ final class PbapClientStateMachine extends StateMachine { break; case MSG_RESUME_DOWNLOAD: mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); downloadIfReady(); break; default: Loading @@ -333,6 +329,21 @@ final class PbapClientStateMachine extends StateMachine { } } /** * Trigger a contacts download if the user is unlocked and our accounts are available to us */ private void downloadIfReady() { boolean userReady = mUserManager.isUserUnlocked(); boolean accountServiceReady = mService.isAuthenticationServiceReady(); if (!userReady || !accountServiceReady) { Log.w(TAG, "Cannot download contacts yet, userReady=" + userReady + ", accountServiceReady=" + accountServiceReady); return; } mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { if (device == null) { Log.w(TAG, "onConnectionStateChanged with invalid device"); Loading @@ -357,7 +368,7 @@ final class PbapClientStateMachine extends StateMachine { sendMessage(MSG_DISCONNECT, device); } public void resumeDownload() { public void tryDownloadIfConnected() { sendMessage(MSG_RESUME_DOWNLOAD); } Loading