| /* |
| * Copyright (c) 2020 The Linux Foundation. All rights reserved. |
| * |
| * 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. |
| */ |
| |
| /** |
| * Bluetooth Bassclient StateMachine. There is one instance per remote device. |
| * - "Disconnected" and "Connected" are steady states. |
| * - "Connecting" and "Disconnecting" are transient states until the |
| * connection / disconnection is completed. |
| * - "ConnectedProcessing" is an intermediate state to ensure, there is only |
| * one Gatt transaction from the profile at any point of time |
| * |
| * |
| * (Disconnected) |
| * | ^ |
| * CONNECT | | DISCONNECTED |
| * V | |
| * (Connecting)<--->(Disconnecting) |
| * | ^ |
| * CONNECTED | | DISCONNECT |
| * V | |
| * (Connected) |
| * | ^ |
| * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT |
| * V | |
| * (ConnectedProcessing) |
| * NOTES: |
| * - If state machine is in "Connecting" state and the remote device sends |
| * DISCONNECT request, the state machine transitions to "Disconnecting" state. |
| * - Similarly, if the state machine is in "Disconnecting" state and the remote device |
| * sends CONNECT request, the state machine transitions to "Connecting" state. |
| * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and |
| * all other requests (add, update, remove source) operations will be deferred in "ConnectedProcessing" state |
| * - Once the gatt transaction is done (or after a specified timeout of no response), State machine will |
| * move back to "Connected" and try to process the deferred requests as needed |
| * |
| * DISCONNECT |
| * (Connecting) ---------------> (Disconnecting) |
| * <--------------- |
| * CONNECT |
| * |
| */ |
| |
| package com.android.bluetooth.bc; |
| |
| import static android.Manifest.permission.BLUETOOTH_CONNECT; |
| |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothGattCallback; |
| import android.bluetooth.BluetoothGatt; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothGattCharacteristic; |
| import android.bluetooth.BluetoothGattDescriptor; |
| import android.bluetooth.le.ScanResult; |
| import android.bluetooth.le.ScanRecord; |
| import android.bluetooth.le.ScanCallback; |
| import android.bluetooth.BluetoothGattService; |
| import android.bluetooth.BluetoothSyncHelper; |
| import android.bluetooth.BleBroadcastSourceInfo; |
| import android.bluetooth.BleBroadcastSourceChannel; |
| import android.bluetooth.BleBroadcastAudioScanAssistManager; |
| import android.bluetooth.IBleBroadcastAudioScanAssistCallback; |
| import android.bluetooth.BleBroadcastAudioScanAssistCallback; |
| import com.android.bluetooth.Utils; |
| |
| //CSIP related imports |
| ///*_CSIP |
| import com.android.bluetooth.groupclient.GroupService; |
| import android.bluetooth.BluetoothGroupCallback; |
| import android.bluetooth.DeviceGroup; |
| //_CSIP*/ |
| |
| ///*_VCP |
| import android.bluetooth.BluetoothVcp; |
| import com.android.bluetooth.vcp.VcpController; |
| //_VCP*/ |
| |
| import android.bluetooth.le.BluetoothLeScanner; |
| import android.bluetooth.le.PeriodicAdvertisingCallback; |
| import android.bluetooth.le.PeriodicAdvertisingManager; |
| import android.bluetooth.le.PeriodicAdvertisingReport; |
| |
| import android.bluetooth.IBluetoothManager; |
| import android.os.ServiceManager; |
| import android.os.IBinder; |
| |
| import java.util.List; |
| import java.util.Arrays; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import android.content.Intent; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.util.Log; |
| import java.util.UUID; |
| import java.util.Collection; |
| import android.os.UserHandle; |
| import java.lang.IllegalArgumentException; |
| |
| import com.android.bluetooth.btservice.ProfileService; |
| /*_PACS |
| import com.android.bluetooth.pacsclient.PacsClientService; |
| _PACS*/ |
| |
| import com.android.bluetooth.btservice.ServiceFactory; |
| ///*_BMS |
| import com.android.bluetooth.broadcast.BroadcastService; |
| //_BMS*/ |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.Scanner; |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| import java.lang.String; |
| import java.lang.StringBuffer; |
| import java.lang.Integer; |
| |
| import java.nio.ByteBuffer; |
| import java.lang.Byte; |
| import java.util.stream.IntStream; |
| import android.os.SystemProperties; |
| import android.os.ParcelUuid; |
| |
| |
| final class BassClientStateMachine extends StateMachine { |
| private static final String TAG = "BassClientStateMachine"; |
| public static final boolean BASS_DBG = true; |
| //public static final boolean BASS_DBG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| private boolean mIsWhitelist = false; |
| |
| static final int BCAST_RECEIVER_STATE_LENGTH = 15; |
| static final int CONNECT = 1; |
| static final int DISCONNECT = 2; |
| static final int CONNECTION_STATE_CHANGED = 3; |
| static final int GATT_TXN_PROCESSED = 4; |
| static final int READ_BASS_CHARACTERISTICS= 5; |
| static final int START_SCAN_OFFLOAD = 6; |
| static final int STOP_SCAN_OFFLOAD = 7; |
| static final int SELECT_BCAST_SOURCE = 8; |
| static final int ADD_BCAST_SOURCE = 9; |
| static final int UPDATE_BCAST_SOURCE = 10; |
| static final int SET_BCAST_CODE = 11; |
| static final int REMOVE_BCAST_SOURCE = 12; |
| static final int GATT_TXN_TIMEOUT = 13; |
| static final int PSYNC_ACTIVE_TIMEOUT = 14; |
| public static final int CSIP_CONNECTION_STATE_CHANGED = 15; |
| static final int CONNECT_TIMEOUT = 16; |
| |
| //30 secs time out for all gatt writes |
| static final int GATT_TXN_TIMEOUT_MS = 30000; |
| |
| //3 min time out for keeping PSYNC active |
| static final int PSYNC_ACTIVE_TIMEOUT_MS = 3*60000; |
| //2 secs time out achieving psync |
| static final int PSYNC_TIMEOUT = 200; |
| |
| int NUM_OF_BROADCAST_RECEIVER_STATES = 0; |
| |
| private final Disconnected mDisconnected; |
| private final Connected mConnected; |
| private final Connecting mConnecting; |
| private final Disconnecting mDisconnecting; |
| private final ConnectedProcessing mConnectedProcessing; |
| private int mLastConnectionState = -1; |
| private static int mConnectTimeoutMs = 30000; |
| private boolean mMTUChangeRequested = false; |
| private boolean mDiscoveryInitiated = false; |
| |
| private BCService mService; |
| private final BluetoothDevice mDevice; |
| private BluetoothGatt mBluetoothGatt = null; |
| |
| //BASS Characteristics UUID |
| public static final UUID BASS_UUID = UUID.fromString("0000184F-0000-1000-8000-00805F9B34FB"); |
| private static final UUID BASS_BCAST_AUDIO_SCAN_CTRL_POINT = UUID.fromString("00002BC7-0000-1000-8000-00805F9B34FB"); |
| private static final UUID BASS_BCAST_RECEIVER_STATE = UUID.fromString("00002BC8-0000-1000-8000-00805F9B34FB"); |
| private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString( |
| "00002902-0000-1000-8000-00805f9b34fb"); |
| private List<BluetoothGattCharacteristic> mBroadcastReceiverStates; |
| private BluetoothGattCharacteristic mBroadcastScanControlPoint; |
| /*key is combination of sourceId, Address and advSid for this hashmap*/ |
| private final Map<Integer, BleBroadcastSourceInfo> mBleBroadcastSourceInfos; |
| private boolean mFirstTimeBisDiscovery = false; |
| private int mPASyncRetryCounter = 0; |
| private ScanResult mScanRes = null; |
| |
| BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
| private ServiceFactory mFactory = new ServiceFactory(); |
| ///*_BMS |
| private BroadcastService mBAService = null; |
| //_BMS*/ |
| |
| private final byte[] REMOTE_SCAN_STOP = {00}; |
| private final byte[] REMOTE_SCAN_START = {01}; |
| private byte BASS_ADD_SOURCE_OPCODE = 0x02; |
| private byte BASS_UPDATE_SOURCE_OPCODE = 0x03; |
| private byte BASS_SET_BCAST_PIN_OPCODE = 0x04; |
| private byte BASS_REMOVE_SOURCE_OPCODE = 0x05; |
| |
| private static int num_of_recever_states = 0; |
| private static int PIN_CODE_CMD_LEN = 18; |
| private final int BASS_MAX_BYTES = 100; |
| private int mPendingOperation = -1; |
| private byte mPendingSourceId = -1; |
| public static byte INVALID_SRC_ID = -1; |
| private int GATT_TXN_TOUT_ERROR = -1; |
| private BleBroadcastSourceInfo mSetBroadcastPINSrcInfo = null; |
| private boolean mSetBroadcastCodePending = false; |
| |
| //types of command for set broadcast PIN operation |
| public int FRESH = 1; |
| private int QUEUED = 2; |
| |
| //types of command for select and add Broadcast source operations |
| public int AUTO = 1; |
| public int USER = 2; |
| |
| //types of operation for Select source to determine |
| //if psync achieved on behalf of single device or multiple devices |
| public int GROUP_OP = 1; |
| public int NON_GROUP_OP = 0; |
| |
| public static int BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC = 0; |
| |
| //Service data Octet0 |
| private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS = 0x00000001; |
| private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS = 0x00000002; |
| |
| //Psync and PAST interfaces |
| private PeriodicAdvertisingManager mPeriodicAdvManager; |
| private static UUID BASIC_AUDIO_UUID = UUID.fromString("00001851-0000-1000-8000-00805F9B34FB"); |
| private boolean mAutoAssist = false; |
| private boolean mNoReverse = false; |
| private boolean mAutoTriggerred = false; |
| private boolean mSyncingOnBehalfOfGroup = false; |
| private boolean mNoStopScanOffload = false; |
| |
| //CSET interfaces |
| ///*_CSIP |
| private GroupService mSetCoordinator = GroupService.getGroupService(); |
| private boolean mCsipConnected = false; |
| //_CSIP*/ |
| |
| private boolean mPacsAvail = false; |
| private boolean mDefNoPAS = false; |
| private boolean mNoPast = false; |
| private boolean mNoCSIPReconn = false; |
| private boolean mPublicAddrForcSrc = false; |
| private boolean mForceSB = false; |
| private boolean mVcpForBroadcast = false; |
| |
| private int BROADCAST_SOURCE_ID_LENGTH = 3; |
| private byte mTempSourceId = 0; |
| //broadcast receiver state indicies |
| private static final int BCAST_RCVR_STATE_SRC_ID_IDX = 0; |
| private static final int BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX = 1; |
| private static final int BCAST_RCVR_STATE_SRC_ADDR_START_IDX = 2; |
| private static final int BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX = 9; |
| private static final int BCAST_RCVR_STATE_SRC_ADDR_SIZE = 6; |
| private static final int BCAST_RCVR_STATE_SRC_ADV_SID_IDX = 8; |
| private static final int BCAST_RCVR_STATE_PA_SYNC_IDX = 12; |
| private static final int BCAST_RCVR_STATE_ENC_STATUS_IDX = 13; |
| private static final int BCAST_RCVR_STATE_BADCODE_START_IDX = 14; |
| private static final int BCAST_RCVR_STATE_BADCODE_SIZE = 16; |
| |
| |
| private static final int BCAST_RCVR_STATE_BIS_SYNC_START_IDX = 10; |
| private static final int BCAST_RCVR_STATE_BIS_SYNC_SIZE = 4; |
| private static final int BCAST_RCVR_STATE_METADATA_LENGTH_IDX = 15; |
| private static final int BCAST_RCVR_STATE_METADATA_START_IDX = 16; |
| BassClientStateMachine(BluetoothDevice device, BCService svc, |
| Looper looper) { |
| super(TAG + "(" + device.toString() + ")", looper); |
| mDevice = device; |
| mService = svc; |
| |
| mDisconnected = new Disconnected(); |
| mDisconnecting = new Disconnecting(); |
| mConnected = new Connected(); |
| mConnecting = new Connecting(); |
| mConnectedProcessing = new ConnectedProcessing(); |
| |
| addState(mDisconnected); |
| addState(mDisconnecting); |
| addState(mConnected); |
| addState(mConnecting); |
| addState(mConnectedProcessing); |
| |
| setInitialState(mDisconnected); |
| mBroadcastReceiverStates = new ArrayList<BluetoothGattCharacteristic>(); |
| mBleBroadcastSourceInfos = new HashMap<Integer, BleBroadcastSourceInfo>(); |
| |
| //PSYNC and PAST instances |
| mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
| if (mBluetoothAdapter != null) { |
| mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager(); |
| } |
| ///*_BMS |
| mBAService = BroadcastService.getBroadcastService(); |
| //_BMS*/ |
| |
| mNoReverse = SystemProperties.getBoolean("persist.vendor.service.bt.nReverse", false); |
| mAutoAssist = SystemProperties.getBoolean("persist.vendor.service.bt.autoassist", false); |
| mIsWhitelist = SystemProperties.getBoolean("persist.vendor.service.bt.wl", true); |
| mDefNoPAS = SystemProperties.getBoolean("persist.vendor.service.bt.defNoPAS", false); |
| mNoPast = SystemProperties.getBoolean("persist.vendor.service.bt.noPast", false); |
| mNoCSIPReconn = SystemProperties.getBoolean("persist.vendor.service.bt.noCsipRec", false); |
| mPublicAddrForcSrc = SystemProperties.getBoolean("persist.vendor.service.bt.pAddrForcSource", true); |
| mForceSB = SystemProperties.getBoolean("persist.vendor.service.bt.forceSB", false); |
| mVcpForBroadcast = SystemProperties.getBoolean("persist.vendor.service.bt.vcpForBroadcast", true); |
| } |
| |
| static BassClientStateMachine make(BluetoothDevice device, BCService svc, |
| Looper looper) { |
| Log.d(TAG, "make for device " + device); |
| BassClientStateMachine BassclientSm = new BassClientStateMachine(device, svc, |
| looper); |
| BassclientSm.start(); |
| return BassclientSm; |
| } |
| |
| public void doQuit() { |
| log("doQuit for device " + mDevice); |
| quitNow(); |
| } |
| |
| public void cleanup() { |
| log("cleanup for device " + mDevice); |
| clearCharsCache(); |
| |
| if (mBluetoothGatt != null) { |
| log("disconnect gatt"); |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| } |
| mPendingOperation = -1; |
| mPendingSourceId = -1; |
| } |
| |
| BleBroadcastSourceInfo getBroadcastSourceInfoForSourceDevice (BluetoothDevice srcDevice) { |
| List<BleBroadcastSourceInfo> currentSourceInfos = |
| getAllBroadcastSourceInformation(); |
| BleBroadcastSourceInfo srcInfo = null; |
| for (int i=0; i<currentSourceInfos.size(); i++) { |
| BluetoothDevice device = currentSourceInfos.get(i).getSourceDevice(); |
| if (device != null && device.equals(srcDevice)) { |
| srcInfo = currentSourceInfos.get(i); |
| Log.e(TAG, "getBroadcastSourceInfoForSourceDevice: returns for: " + srcDevice + "&srcInfo" + srcInfo); |
| return srcInfo; |
| } |
| } |
| return null; |
| } |
| |
| BleBroadcastSourceInfo getBroadcastSourceInfoForSourceId (int srcId) { |
| List<BleBroadcastSourceInfo> currentSourceInfos = |
| getAllBroadcastSourceInformation(); |
| BleBroadcastSourceInfo srcInfo = null; |
| for (int i=0; i<currentSourceInfos.size(); i++) { |
| int sId = currentSourceInfos.get(i).getSourceId(); |
| if (srcId == sId) { |
| srcInfo = currentSourceInfos.get(i); |
| Log.e(TAG, "getBroadcastSourceInfoForSourceId: returns for: " + srcId + "&srcInfo" + srcInfo); |
| return srcInfo; |
| } |
| } |
| return null; |
| } |
| |
| void parseBaseData(BluetoothDevice device, int syncHandle, byte[] serviceData) { |
| log("parseBaseData" + Arrays.toString(serviceData)); |
| BaseData base_ = new BaseData(serviceData); |
| if (base_ != null) { |
| mService.updateBASE(syncHandle, base_); |
| base_.print(); |
| base_.printConsolidated(); |
| if (mAutoTriggerred == false) { |
| mService.sendBroadcastSourceSelectedCallback(device, base_.getBroadcastChannels(), |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); |
| } else { |
| //successfull auto periodic synchrnization with source |
| log("auto triggered assist"); |
| mAutoTriggerred = false; |
| //perform PAST with this device |
| BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); |
| if (srcDevice != null) { |
| BleBroadcastSourceInfo srcInfo = getBroadcastSourceInfoForSourceDevice(srcDevice); |
| processPASyncState(srcInfo); |
| } else { |
| Log.w(TAG, "Autoassist: no matching device"); |
| } |
| } |
| } else { |
| // |
| Log.e(TAG, "Seems BASE is not in parsable format"); |
| if (mAutoTriggerred == false) { |
| mService.sendBroadcastSourceSelectedCallback(device, base_.getBroadcastChannels(), |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); |
| BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); |
| cancelActiveSync(srcDevice); |
| } else { |
| mAutoTriggerred = false; |
| } |
| } |
| } |
| |
| void parseScanRecord(int syncHandle, ScanRecord record) { |
| log("parseScanRecord" + record); |
| BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); |
| Map<ParcelUuid, byte[]> bmsAdvDataMap = record.getServiceData(); |
| if (bmsAdvDataMap != null) { |
| for (Map.Entry<ParcelUuid,byte[]> entry : bmsAdvDataMap.entrySet()) { |
| log("ParcelUUid = " + entry.getKey() + |
| ", Value = " + entry.getValue()); |
| } |
| } else { |
| log("bmsAdvDataMap is null"); |
| if (mAutoTriggerred == false) { |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); |
| cancelActiveSync(srcDevice); |
| } else { |
| mAutoTriggerred = false; |
| } |
| } |
| ParcelUuid basicAudioUuid = new ParcelUuid(BASIC_AUDIO_UUID); |
| byte[] bmsAdvData = record.getServiceData(basicAudioUuid); |
| if (bmsAdvData != null) { |
| //ByteBuffer bb = ByteBuffer.wrap(bmsAdvData); |
| parseBaseData(mDevice, syncHandle, bmsAdvData); |
| |
| } else { |
| Log.e(TAG, "No service data in Scan record"); |
| if (mAutoTriggerred == false) { |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); |
| cancelActiveSync(srcDevice); |
| } else { |
| mAutoTriggerred = false; |
| } |
| } |
| } |
| |
| /*Local Public address based check |
| Use this prior to addition of Broadcast source*/ |
| boolean isLocalBroadcastSource (BluetoothDevice device) |
| { |
| BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); |
| boolean ret = btAdapter.getAddress().equals(device.getAddress()); |
| |
| log("isLocalBroadcastSource returns" +ret); |
| return ret; |
| } |
| |
| private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { |
| boolean ret = true; |
| List<BleBroadcastSourceInfo> currentSourceInfos = |
| getAllBroadcastSourceInformation(); |
| Log.i(TAG, "input srcInfo: " + srcInfo); |
| for (int i=0; i<currentSourceInfos.size(); i++) { |
| Log.i(TAG, "srcInfo: [" + i + "]" + currentSourceInfos.get(i)); |
| if (srcInfo.matches(currentSourceInfos.get(i))) { |
| ret = false; |
| break; |
| } |
| } |
| |
| log("isValidBroadcastSourceInfo returns: " + ret); |
| return ret; |
| } |
| |
| public boolean selectBroadcastSource (ScanResult scanRes, boolean groupOp, boolean autoTriggerred) { |
| Log.i(TAG, "selectBroadcastSource for " + "isGroupOp:" + groupOp); |
| Log.i(TAG, "ScanResult " + scanRes); |
| int broadcastId = BCService.INVALID_BROADCAST_ID; |
| if (isLocalBroadcastSource(scanRes.getDevice()) != true) { |
| mAutoTriggerred = autoTriggerred; |
| mFirstTimeBisDiscovery = true; |
| mPASyncRetryCounter = 1; |
| //Cache Scan res for Retrys |
| mScanRes = scanRes; |
| /*This is an override case |
| if Previous sync is still active, cancel It |
| But dont stop the Scan offload as we still trying to assist remote*/ |
| mNoStopScanOffload = true; |
| cancelActiveSync(null); |
| mService.getBassUtils().leScanControl(true); |
| try { |
| mPeriodicAdvManager.registerSync(scanRes, 0, |
| PSYNC_TIMEOUT, mPeriodicAdvCallback); |
| mSyncingOnBehalfOfGroup = groupOp; |
| } catch (IllegalArgumentException ex) { |
| Log.w(TAG, "registerSync:IllegalArguementException"); |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); |
| mService.stopScanOffloadInternal(mDevice, false); |
| return false; |
| } |
| //updating mainly for Address type and PA Interval here |
| //extract BroadcastId from ScanResult |
| ScanRecord scanRecord = scanRes.getScanRecord(); |
| if (scanRecord != null) { |
| Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData(); |
| if (listOfUuids != null) { |
| if(listOfUuids.containsKey(ParcelUuid.fromString(BassUtils.BAAS_UUID))) { |
| byte[] bId = listOfUuids.get(ParcelUuid.fromString(BassUtils.BAAS_UUID)); |
| broadcastId = (0x00FF0000 & (bId[0] << 16)); |
| broadcastId |= (0x0000FF00 & (bId[1] << 8)); |
| broadcastId |= (0x000000FF & bId[2]); |
| } |
| } |
| mService.updatePAResultsMap(scanRes.getDevice(), scanRes.getAddressType(), |
| BCService.INVALID_SYNC_HANDLE, BCService.INVALID_ADV_SID, |
| scanRes.getPeriodicAdvertisingInterval(), |
| broadcastId); |
| } |
| } |
| else { |
| log("colocated case"); |
| if (autoTriggerred) { |
| log("should never happen!!!"); |
| //ignore |
| mAutoTriggerred = false; |
| } |
| ///*_BMS |
| if (mBAService == null || mBAService.isBroadcastActive() != true) { |
| Log.e(TAG, "colocated source handle unavailable OR not in streaming"); |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); |
| mService.stopScanOffloadInternal(mDevice, false); |
| return false; |
| } |
| String colocatedAddress = null; |
| int colocatedAddressType; |
| if (mPublicAddrForcSrc == true) { |
| colocatedAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); |
| colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; |
| } else { |
| colocatedAddress = mBAService.BroadcastGetAdvAddress(); |
| colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); |
| } |
| int paInterval = 0x0000FFFF; |
| paInterval = mBAService.BroadcastGetAdvInterval(); |
| |
| if (colocatedAddress == null) { |
| log("colocatedAddress is null"); |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); |
| mService.stopScanOffloadInternal(mDevice, false); |
| return false; |
| } |
| BluetoothDevice colocatedSrcDevice = |
| BluetoothAdapter.getDefaultAdapter().getRemoteDevice(colocatedAddress); |
| log("caching local Broacast details: " + colocatedSrcDevice); |
| |
| //advSid is same as advHandle for collocated case |
| byte[] broadcast_id = mBAService.getBroadcastId(); |
| broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); |
| broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); |
| broadcastId |= (0x000000FF & broadcast_id[0]); |
| |
| mService.updatePAResultsMap(colocatedSrcDevice, colocatedAddressType, |
| mBAService.BroadcatGetAdvHandle(), |
| mBAService.BroadcatGetAdvHandle(), |
| paInterval, |
| broadcastId); |
| BaseData localBase = new BaseData(mBAService.getNumSubGroups(), |
| mBAService.BroadcastGetBisInfo(), |
| mBAService.BroadcastGetMetaInfo()); |
| localBase.printConsolidated(); |
| //Use advHandle to cahce Base |
| mService.updateBASE(mBAService.BroadcatGetAdvHandle(), localBase); |
| mService.sendBroadcastSourceSelectedCallback(mDevice, localBase.getBroadcastChannels(), |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); |
| //_BMS*/ |
| } |
| return true; |
| } |
| |
| private void cancelActiveSync(BluetoothDevice sourceDev) { |
| log("cancelActiveSync"); |
| boolean isCancelSyncNeeded = false; |
| BluetoothDevice activeSyncedSrc = mService.getActiveSyncedSource(mDevice); |
| if (activeSyncedSrc != null ) { |
| if (sourceDev == null) { |
| isCancelSyncNeeded = true; |
| } else if(activeSyncedSrc.equals(sourceDev)) { |
| isCancelSyncNeeded = true; |
| } |
| } |
| if (isCancelSyncNeeded) { |
| removeMessages(PSYNC_ACTIVE_TIMEOUT); |
| try { |
| log("calling unregisterSync"); |
| mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback); |
| } catch (IllegalArgumentException ex) { |
| Log.w(TAG, "unregisterSync:IllegalArguementException"); |
| //ignore |
| } |
| mService.clearPAResults(activeSyncedSrc); |
| mService.setActiveSyncedSource(mDevice, null); |
| if (mNoStopScanOffload != true) { |
| //trigger scan stop here |
| mService.stopScanOffloadInternal(mDevice, false); |
| } |
| } |
| mNoStopScanOffload = false; |
| } |
| |
| /* Internal periodc Advertising manager callback |
| * |
| */ |
| private PeriodicAdvertisingCallback mPeriodicAdvCallback = new PeriodicAdvertisingCallback() { |
| @Override |
| public void onSyncEstablished(int syncHandle, BluetoothDevice device, |
| int advertisingSid, int skip, int timeout, |
| int status) { |
| log ("onSyncEstablished" + "syncHandle" + syncHandle + |
| "device" + device + "advertisingSid" + advertisingSid + |
| "skip" + skip + "timeout" + timeout + "status" + |
| status); |
| //turn off the LeScan once sync estd |
| mService.getBassUtils().leScanControl(false); |
| if (status == BluetoothGatt.GATT_SUCCESS) { |
| //upates syncHandle, advSid |
| mService.updatePAResultsMap(device, |
| BCService.INVALID_ADV_ADDRESS_TYPE, |
| syncHandle, advertisingSid, |
| BCService.INVALID_ADV_INTERVAL, |
| BCService.INVALID_BROADCAST_ID); |
| sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT, PSYNC_ACTIVE_TIMEOUT_MS); |
| mService.setActiveSyncedSource(mDevice, device); |
| } else { |
| log("failed to sync to PA" + mPASyncRetryCounter); |
| mScanRes = null; |
| if (mAutoTriggerred == false) { |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); |
| mService.stopScanOffloadInternal(mDevice, false); |
| } |
| mAutoTriggerred = false; |
| } |
| } |
| @Override |
| public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { |
| log( "onPeriodicAdvertisingReport"); |
| //Parse the BIS indicies from report's service data |
| if (mFirstTimeBisDiscovery) { |
| parseScanRecord(report.getSyncHandle(),report.getData()); |
| mFirstTimeBisDiscovery = false; |
| } |
| } |
| @Override |
| public void onSyncLost(int syncHandle) { |
| log( "OnSyncLost" + syncHandle); |
| BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); |
| cancelActiveSync(srcDevice); |
| } |
| |
| public void onSyncTransfered(BluetoothDevice device, int status) { |
| log("sync transferred:" + device + " : " + status); |
| } |
| }; |
| |
| private void broadcastReceiverState(BleBroadcastSourceInfo state, int index, int max_num_srcInfos) { |
| log("broadcastReceiverState: " + mDevice); |
| |
| Intent intent = new Intent(BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); |
| intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO, state); |
| intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO_INDEX, index); |
| intent.putExtra(BleBroadcastSourceInfo.EXTRA_MAX_NUM_SOURCE_INFOS, max_num_srcInfos); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
| | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, |
| Utils.getTempAllowlistBroadcastOptions()); |
| } |
| |
| private static boolean isEmpty(final byte[] data){ |
| return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0); |
| } |
| |
| private void processPASyncState(BleBroadcastSourceInfo srcInfo) { |
| log("processPASyncState" + srcInfo); |
| int serviceData = 0; |
| if (srcInfo == null) { |
| Log.e(TAG, "processPASyncState: srcInfo is null"); |
| return; |
| } |
| if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ) { |
| log("Initiate PAST procedure"); |
| BCService.PAResults res = mService.getPAResults(srcInfo.getSourceDevice()); |
| if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) && |
| mService.isLocalBroadcasting()) { |
| if (res == null) { |
| log("Populate colocated PA and initiate PAST"); |
| |
| int colocatedAddressType; |
| if (mPublicAddrForcSrc == true) { |
| colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; |
| } else { |
| colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); |
| } |
| int broadcastId; |
| byte[] broadcast_id = mBAService.getBroadcastId(); |
| broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); |
| broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); |
| broadcastId |= (0x000000FF & broadcast_id[0]); |
| mService.updatePAResultsMap(srcInfo.getSourceDevice(), colocatedAddressType, |
| mBAService.BroadcatGetAdvHandle(), |
| mBAService.BroadcatGetAdvHandle(), |
| mBAService.BroadcastGetAdvInterval(), |
| broadcastId); |
| } |
| res = mService.getPAResults(srcInfo.getSourceDevice()); |
| } |
| if (res != null) { |
| int syncHandle = res.mSyncHandle; |
| log("processPASyncState: syncHandle" + res.mSyncHandle); |
| if (mNoPast == false && syncHandle != BCService.INVALID_SYNC_HANDLE) { |
| if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { |
| log("Collocated Case Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + |
| "serviceData" + serviceData); |
| serviceData = 0x000000FF & srcInfo.getSourceId(); |
| serviceData = serviceData << 8; |
| //advA matches EXT_ADV_ADDRESS |
| //but not matches source address (as we would have written pAddr) |
| serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); |
| serviceData = serviceData | (BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); |
| try { |
| mPeriodicAdvManager.transferSetInfo(mDevice, serviceData, syncHandle,mPeriodicAdvCallback); |
| } catch (IllegalArgumentException ex) { |
| Log.w(TAG, "transferSetInfo: IllegalArgumentException : PAST failure"); |
| //ignore |
| } |
| } else { |
| serviceData = 0x000000FF & srcInfo.getSourceId(); |
| serviceData = serviceData << 8; |
| //advA matches EXT_ADV_ADDRESS |
| //also matches source address (as we would have written) |
| serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); |
| serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); |
| log("Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + |
| "serviceData" + serviceData); |
| mPeriodicAdvManager.transferSync(mDevice, serviceData, syncHandle); |
| } |
| } |
| } else { |
| Log.e(TAG, "There is no valid sync handle for this Source"); |
| if (mAutoAssist) { |
| //initiate Auto Assist procedure for this device |
| mService.getBassUtils().triggerAutoAssist (srcInfo); |
| } |
| } |
| } |
| else if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED || |
| srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST) { |
| //Cancel the existing sync and Invalidate the sync handle |
| if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) == false) { |
| if (mSyncingOnBehalfOfGroup == false) { |
| //Cancel the Sync only If it is NOT syced on behalf of group. |
| //group based sync will be kept active PSYNC_ACTIVE_TIMEOUT seconds so that |
| //all group members can get back in sync |
| log("Unregister sync as It is non colocated"); |
| cancelActiveSync(srcInfo.getSourceDevice()); |
| } |
| } else { |
| //trigger scan stop here |
| mService.stopScanOffloadInternal(mDevice, false); |
| } |
| } |
| } |
| |
| /*Actual OTA advertising address based check |
| Use this after the addition of Broadcast source*/ |
| private boolean isAddedBroadcastSourceIsLocal (BluetoothDevice device) |
| { |
| if (device == null) { |
| Log.e(TAG, "device handle is null"); |
| return false; |
| } |
| String localBroadcasterAddr = null; |
| ///*_BMS |
| if (mPublicAddrForcSrc) { |
| localBroadcasterAddr = BluetoothAdapter.getDefaultAdapter().getAddress(); |
| } else { |
| if (mBAService == null) { |
| mBAService = BroadcastService.getBroadcastService(); |
| } |
| if (mBAService == null || mBAService.isBroadcastActive() != true) { |
| Log.e(TAG, "isAddedBroadcastSourceIsLocal: colocated source handle is unavailable"); |
| return false; |
| } |
| localBroadcasterAddr = mBAService.BroadcastGetAdvAddress(); |
| } |
| //_BMS*/ |
| boolean ret = false; |
| if (localBroadcasterAddr == null) { |
| Log.e(TAG, "isAddedBroadcastSourceIsLocal: localBroadcasterAddr is null"); |
| ret = false; |
| } else { |
| ret = localBroadcasterAddr.equals(device.getAddress()); |
| } |
| log("isAddedBroadcastSourceIsLocal returns" +ret); |
| return ret; |
| } |
| |
| private void checkAndUpdateBroadcastCode(BleBroadcastSourceInfo srcInfo) { |
| if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { |
| if (mForceSB == true || |
| srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { |
| //query the Encryption Key from BMS and update |
| ///*_BMS |
| byte[] colocatedBcastCode = mBAService.GetEncryptionKey(null); |
| if (mBAService.isBroadcastStreamingEncrypted() == false) { |
| Log.e(TAG, "seem to be Unencrypted colocated broadcast"); |
| //do nothing |
| return; |
| } |
| log("colocatedBcastCode is " + colocatedBcastCode); |
| //queue a fresh command to update the |
| Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); |
| m.obj = srcInfo; |
| m.arg1 = FRESH; |
| log("checkAndUpdateBroadcastCode: src device: " + srcInfo.getSourceDevice()); |
| sendMessage(m); |
| //_BMS*/ |
| } |
| } else { |
| log("checkAndUpdateBroadcastCode"); |
| //non colocated case, Broadcast PIN should have been updated from lyaer |
| //If there is pending one process it Now |
| if (srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED && |
| mSetBroadcastCodePending == true) { |
| //Make a QUEUED command |
| log("Update the Broadcast now"); |
| Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); |
| m.obj = mSetBroadcastPINSrcInfo; |
| m.arg1 = QUEUED; |
| |
| sendMessage(m); |
| mSetBroadcastCodePending = false; |
| mSetBroadcastPINSrcInfo = null; |
| } |
| } |
| } |
| |
| private List<BleBroadcastSourceChannel> getListOfBisIndicies(int bisIndicies, int subGroupId, byte[] metaData) { |
| List<BleBroadcastSourceChannel> bcastIndicies = new ArrayList<BleBroadcastSourceChannel>(); |
| int index =0; |
| log("getListOfBisIndicies:" + bisIndicies); |
| while (bisIndicies != 0) { |
| if ((bisIndicies & 0x00000001) == 0x00000001) { |
| BleBroadcastSourceChannel bI = |
| new BleBroadcastSourceChannel(index, Integer.toString(index), true, subGroupId, metaData); |
| bcastIndicies.add(bI); |
| log("Adding BIS index for :" + index); |
| } |
| bisIndicies = bisIndicies>>1; |
| index++; |
| } |
| return bcastIndicies; |
| |
| } |
| |
| private void processBroadcastReceiverState (byte[] receiverState, BluetoothGattCharacteristic characteristic) { |
| int index = -1; |
| boolean isEmptyEntry = false; |
| BleBroadcastSourceInfo srcInfo = null; |
| |
| log("processBroadcastReceiverState:: characteristic:" + characteristic); |
| |
| byte sourceId = 0; |
| if (receiverState.length > 0) |
| sourceId = receiverState[BCAST_RCVR_STATE_SRC_ID_IDX]; |
| log("processBroadcastReceiverState: receiverState length: " + receiverState.length); |
| if (receiverState.length == 0 || |
| isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length-1))) { |
| log("This is an Empty Entry"); |
| if (mPendingOperation == REMOVE_BCAST_SOURCE) { |
| srcInfo = new BleBroadcastSourceInfo(mPendingSourceId); |
| } else if (receiverState.length == 0) { |
| if (mBleBroadcastSourceInfos != null) { |
| mTempSourceId = (byte)mBleBroadcastSourceInfos.size(); |
| } |
| if (mTempSourceId < NUM_OF_BROADCAST_RECEIVER_STATES) { |
| mTempSourceId++; |
| srcInfo = new BleBroadcastSourceInfo(mTempSourceId); |
| } else { |
| Log.e(TAG, "reached the remote supported max SourceInfos"); |
| return; |
| } |
| } |
| isEmptyEntry = true; |
| //just create an Empty entry |
| if (srcInfo.isEmptyEntry()) { |
| log("An empty entry has been created"); |
| } |
| } |
| else { |
| byte sourceAddressType = receiverState[BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX]; |
| byte[] sourceAddress = new byte[BCAST_RCVR_STATE_SRC_ADDR_SIZE]; |
| System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_ADDR_START_IDX, sourceAddress, 0, BCAST_RCVR_STATE_SRC_ADDR_SIZE); |
| byte sourceAdvSid = receiverState[BCAST_RCVR_STATE_SRC_ADV_SID_IDX]; |
| |
| byte[] broadcastIdBytes = new byte[BROADCAST_SOURCE_ID_LENGTH]; |
| System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX, broadcastIdBytes, 0, BROADCAST_SOURCE_ID_LENGTH); |
| int broadcastId = (0x00FF0000 & (broadcastIdBytes[2] << 16)); |
| broadcastId |= (0x0000FF00 & (broadcastIdBytes[1] << 8)); |
| broadcastId |= (0x000000FF & broadcastIdBytes[0]); |
| |
| BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); |
| BluetoothDevice device = btAdapter.getRemoteDevice(reverseBytes(sourceAddress)); |
| byte metaDataSyncState = receiverState[BCAST_RCVR_STATE_PA_SYNC_IDX]; |
| |
| |
| byte encyptionStatus = receiverState[BCAST_RCVR_STATE_ENC_STATUS_IDX]; |
| byte[] badBroadcastCode = null; |
| byte badBroadcastCodeLen = 0; |
| byte numSubGroups = 0; |
| byte[] metadataLength = null; |
| byte[] metaData = null; |
| if (encyptionStatus == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_BADCODE) { |
| badBroadcastCode = new byte[BCAST_RCVR_STATE_BADCODE_SIZE]; |
| System.arraycopy(receiverState, BCAST_RCVR_STATE_BADCODE_START_IDX, badBroadcastCode, 0, BCAST_RCVR_STATE_BADCODE_SIZE); |
| badBroadcastCode = reverseBytes(badBroadcastCode); |
| badBroadcastCodeLen = BCAST_RCVR_STATE_BADCODE_SIZE; |
| } |
| numSubGroups = receiverState[BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen]; |
| int offset = BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen + 1; |
| //Map of Bis Status <subGroupId, List_OF_Broadcast_channel> |
| Map<Integer, List<BleBroadcastSourceChannel>> bisIndexList = new HashMap<Integer, List<BleBroadcastSourceChannel>>(); |
| //Map for Metada <subGroupId, Metadata> |
| Map<Integer, byte[]> metadataList = new HashMap<Integer, byte[]>(); |
| metadataLength = new byte[numSubGroups]; |
| byte audioSyncState = 0; |
| for (int i = 0; i < numSubGroups; i++) { |
| byte[] audioSyncIndex = new byte[BCAST_RCVR_STATE_BIS_SYNC_SIZE]; |
| System.arraycopy(receiverState, offset, audioSyncIndex, 0, BCAST_RCVR_STATE_BIS_SYNC_SIZE); |
| offset = offset + BCAST_RCVR_STATE_BIS_SYNC_SIZE; |
| log("BIS index byte array: "); |
| BassUtils.printByteArray(audioSyncIndex); |
| ByteBuffer wrapped = ByteBuffer.wrap(reverseBytes(audioSyncIndex)); |
| int audioBisIndex = wrapped.getInt(); |
| if (audioBisIndex == 0xFFFFFFFF) { |
| log("Remote failed to sync to BIS"); |
| audioSyncState = 0x00; |
| audioBisIndex = 0; |
| } else { |
| //Bits (0-30)=> (1-31) |
| audioBisIndex = audioBisIndex << 1; |
| log("BIS index converted: " + audioBisIndex); |
| if (audioBisIndex != 0){ |
| //If any BIS is in sync, Set Audio state as ON |
| audioSyncState = 0x01; |
| } |
| } |
| |
| metadataLength[i] = receiverState[offset++]; |
| if (metadataLength[i] != 0) { |
| log("metadata of length: " + metadataLength[i] + "is avaialble"); |
| metaData = new byte[metadataLength[i]]; |
| System.arraycopy(receiverState, offset, metaData, 0, metadataLength[i]); |
| offset = offset + metadataLength[i]; |
| metaData = reverseBytes(metaData); |
| metadataList.put(i, metaData); |
| } |
| bisIndexList.put(i, getListOfBisIndicies(audioBisIndex, i, metaData)); |
| } |
| srcInfo = new BleBroadcastSourceInfo(device, |
| sourceId, |
| sourceAdvSid, |
| broadcastId, |
| (int)sourceAddressType, |
| (int)metaDataSyncState, |
| (int)encyptionStatus, |
| badBroadcastCode, |
| numSubGroups, |
| (int)audioSyncState, |
| bisIndexList, |
| metadataList |
| ); |
| } |
| BleBroadcastSourceInfo oldSourceInfo = mBleBroadcastSourceInfos.get(characteristic.getInstanceId()); |
| if (oldSourceInfo == null) { |
| log("Initial Read and Populating values"); |
| if (mBleBroadcastSourceInfos.size() == NUM_OF_BROADCAST_RECEIVER_STATES) { |
| Log.e(TAG, "reached the Max SourceInfos"); |
| return; |
| } |
| mBleBroadcastSourceInfos.put(characteristic.getInstanceId(), srcInfo); |
| checkAndUpdateBroadcastCode(srcInfo); |
| processPASyncState(srcInfo); |
| } else { |
| log("old sourceInfo: " + oldSourceInfo); |
| log("new sourceInfo: " + srcInfo); |
| mBleBroadcastSourceInfos.replace(characteristic.getInstanceId(), srcInfo); |
| if (oldSourceInfo.isEmptyEntry() == true) { |
| log("New Source Addition"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE, |
| srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); |
| checkAndUpdateBroadcastCode(srcInfo); |
| processPASyncState(srcInfo); |
| } else { |
| if (isEmptyEntry) { |
| BluetoothDevice removedDevice = oldSourceInfo.getSourceDevice(); |
| log("sourceInfo removal" + removedDevice); |
| cancelActiveSync(removedDevice); |
| sendPendingCallbacks(REMOVE_BCAST_SOURCE, |
| srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); |
| } else { |
| log("update to an existing srcInfo"); |
| sendPendingCallbacks(UPDATE_BCAST_SOURCE, |
| srcInfo.getSourceId(),BluetoothGatt.GATT_SUCCESS); |
| processPASyncState(srcInfo); |
| checkAndUpdateBroadcastCode(srcInfo); |
| } |
| } |
| } |
| index = srcInfo.getSourceId(); |
| if (index == -1) { |
| log("processBroadcastReceiverState: invalid index"); |
| return; |
| } |
| broadcastReceiverState(srcInfo, index, NUM_OF_BROADCAST_RECEIVER_STATES); |
| } |
| // Implements callback methods for GATT events that the app cares about. For example, |
| // connection change and services discovered. |
| private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { |
| @Override |
| public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { |
| boolean isStateChanged = false; |
| log( "onConnectionStateChange : Status=" + status + "newState" + newState); |
| if (newState == BluetoothProfile.STATE_CONNECTED && getConnectionState() != BluetoothProfile.STATE_CONNECTED) { |
| isStateChanged = true; |
| Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice); |
| if (mService.okToConnect(mDevice)) { |
| log("Bassclient Connected to: " + mDevice); |
| if (mBluetoothGatt != null) { |
| log( "Attempting to start service discovery:" + |
| mBluetoothGatt.discoverServices()); |
| mDiscoveryInitiated = true; |
| } |
| } else { |
| if (mBluetoothGatt != null) { |
| // Reject the connection |
| Log.w(TAG, "Bassclient Connect request rejected: " + mDevice); |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| //force move to disconnected |
| newState = BluetoothProfile.STATE_DISCONNECTED; |
| } |
| } |
| } else if (newState == BluetoothProfile.STATE_DISCONNECTED && |
| getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { |
| isStateChanged = true; |
| log( "Disconnected from Bass GATT server."); |
| } |
| if (isStateChanged) { |
| Message m = obtainMessage(CONNECTION_STATE_CHANGED); |
| m.obj = newState; |
| sendMessage(m); |
| } |
| } |
| @Override |
| public void onServicesDiscovered(BluetoothGatt gatt, int status) { |
| log("onServicesDiscovered:" + status); |
| if (mDiscoveryInitiated == true) { |
| mDiscoveryInitiated = false; |
| if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) { |
| mBluetoothGatt.requestMtu(BASS_MAX_BYTES); |
| mMTUChangeRequested = true; |
| } else { |
| Log.w(TAG, "onServicesDiscovered received: " + status |
| + "mBluetoothGatt" + mBluetoothGatt); |
| } |
| } else { |
| log("remote initiated callback"); |
| //do nothing |
| } |
| } |
| @Override |
| public void onCharacteristicRead(BluetoothGatt gatt, |
| BluetoothGattCharacteristic characteristic, |
| int status) { |
| log( "onCharacteristicRead:: status: " + status + "char:" + characteristic); |
| |
| if (status == BluetoothGatt.GATT_SUCCESS && |
| characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { |
| log( "onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status); |
| logByteArray("Received ", characteristic.getValue(), 0, |
| characteristic.getValue().length); |
| if (characteristic.getValue() == null) { |
| Log.e(TAG, "Remote receiver state is NULL"); |
| return; |
| } |
| processBroadcastReceiverState(characteristic.getValue(), characteristic); |
| } |
| // switch to receiving notifications after initial characteristic read |
| BluetoothGattDescriptor desc = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); |
| if (mBluetoothGatt != null && desc != null) { |
| log("Setting the value for Desc"); |
| mBluetoothGatt.setCharacteristicNotification(characteristic, true); |
| desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); |
| mBluetoothGatt.writeDescriptor(desc); |
| } else { |
| Log.w(TAG, "CCC for " + characteristic + "seem to be not present"); |
| //atleast move the SM to stable state |
| Message m = obtainMessage(GATT_TXN_PROCESSED); |
| m.arg1 = status; |
| sendMessage(m); |
| } |
| } |
| |
| @Override |
| public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, |
| int status) { |
| log("onDescriptorWrite"); |
| if (status == BluetoothGatt.GATT_SUCCESS && |
| descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIG)) { |
| log("CCC write resp"); |
| } |
| |
| //Move the SM to connected so further reads happens |
| Message m = obtainMessage(GATT_TXN_PROCESSED); |
| m.arg1 = status; |
| sendMessage(m); |
| } |
| |
| @Override |
| public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) |
| { |
| log("onMtuChanged: mtu:" + mtu); |
| if (mMTUChangeRequested == true && mBluetoothGatt != null) { |
| acquireAllBassChars(); |
| mMTUChangeRequested = false; |
| } else { |
| log("onMtuChanged is remote initiated trigger, mBluetoothGatt:" + mBluetoothGatt); |
| //Do nothing |
| } |
| } |
| |
| @Override |
| public void onCharacteristicChanged(BluetoothGatt gatt, |
| BluetoothGattCharacteristic characteristic) { |
| log( "onCharacteristicChanged :: " |
| + characteristic.getUuid().toString()); |
| if (characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { |
| log( "onCharacteristicChanged is rcvr State :: " |
| + characteristic.getUuid().toString()); |
| if (characteristic.getValue() == null) { |
| Log.e(TAG, "Remote receiver state is NULL"); |
| return; |
| } |
| logByteArray("onCharacteristicChanged: Received ", characteristic.getValue(), 0, |
| characteristic.getValue().length); |
| processBroadcastReceiverState(characteristic.getValue(), characteristic); |
| } |
| } |
| |
| @Override |
| public void onCharacteristicWrite(BluetoothGatt gatt, |
| BluetoothGattCharacteristic characteristic, int status) { |
| log( "onCharacteristicWrite: " |
| + characteristic.getUuid().toString() |
| + "status:" + status); |
| if (status == 0 && |
| characteristic.getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { |
| log( "BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully"); |
| } |
| Message m = obtainMessage(GATT_TXN_PROCESSED); |
| m.arg1 = status; |
| sendMessage(m); |
| } |
| }; |
| |
| public List<BleBroadcastSourceInfo> getAllBroadcastSourceInformation() { |
| log( "getAllBroadcastSourceInformation"); |
| List list = new ArrayList(mBleBroadcastSourceInfos.values()); |
| return list; |
| } |
| |
| void acquireAllBassChars() { |
| clearCharsCache(); |
| BluetoothGattService service = null; |
| if (mBluetoothGatt != null) { |
| log("getting Bass Service handle"); |
| service = mBluetoothGatt.getService(BASS_UUID); |
| } |
| if (service != null) { |
| log( "found BASS_SERVICE"); |
| List<BluetoothGattCharacteristic> allChars = service.getCharacteristics(); |
| int numOfChars = allChars.size(); |
| NUM_OF_BROADCAST_RECEIVER_STATES = numOfChars-1; |
| log( "Total number of chars" + numOfChars); |
| //static var to keep track of read callbacks |
| num_of_recever_states = NUM_OF_BROADCAST_RECEIVER_STATES; |
| for (int i = 0; i < allChars.size(); i++) { |
| if (allChars.get(i).getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { |
| mBroadcastScanControlPoint = allChars.get(i); |
| log( "Index of ScanCtrlPoint:" + i); |
| } else { |
| log( "Reading " + i + "th ReceiverState"); |
| mBroadcastReceiverStates.add(allChars.get(i)); |
| Message m = obtainMessage(READ_BASS_CHARACTERISTICS); |
| m.obj = allChars.get(i); |
| sendMessage(m); |
| } |
| } |
| } else { |
| Log.e(TAG, "acquireAllBassChars: BASS service not found"); |
| } |
| } |
| |
| void clearCharsCache() { |
| if (mBroadcastReceiverStates != null) { |
| mBroadcastReceiverStates.clear(); |
| } |
| if (mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint = null; |
| } |
| num_of_recever_states = 0; |
| if (mBleBroadcastSourceInfos != null) { |
| mBleBroadcastSourceInfos.clear(); |
| } |
| mPendingOperation = -1; |
| } |
| |
| @VisibleForTesting |
| class Disconnected extends State { |
| @Override |
| public void enter() { |
| log( "Enter Disconnected(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| clearCharsCache(); |
| mTempSourceId = 0; |
| removeDeferredMessages(DISCONNECT); |
| |
| if (mLastConnectionState == -1) { |
| log( "no Broadcast of initial profile state "); |
| } else { |
| if (mPacsAvail == true) { |
| /*_PACS |
| PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); |
| if (mPacsClientService != null) { |
| log("trigger disconnect to Pacs"); |
| mPacsClientService.disconnect(mDevice); |
| } else { |
| Log.e(TAG, "PACs interface is null"); |
| } |
| _PACS*/ |
| } |
| |
| ///*_VCP |
| if (mVcpForBroadcast) { |
| VcpController vcpController = VcpController.getVcpController(); |
| if (vcpController != null) { |
| log("trigger disconnect to Vcp Renderer"); |
| if (!vcpController.disconnect(mDevice, BluetoothVcp.MODE_BROADCAST)) { |
| log("Disconnect Vcp failed"); |
| } |
| } else { |
| Log.e(TAG, "VcpController interface is null"); |
| } |
| } |
| //_VCP*/ |
| broadcastConnectionState(mDevice, mLastConnectionState, |
| BluetoothProfile.STATE_DISCONNECTED); |
| } |
| } |
| |
| @Override |
| public void exit() { |
| log("Exit Disconnected(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| log("Disconnected process message(" + mDevice + "): " + messageWhatToString( |
| message.what)); |
| |
| switch (message.what) { |
| case CONNECT: |
| log("Connecting to " + mDevice); |
| if (mBluetoothGatt != null) { |
| Log.d(TAG, "clear off, pending wl connection"); |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| } |
| |
| if ((mBluetoothGatt = mDevice.connectGatt(mService, mIsWhitelist, mGattCallback, |
| BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | |
| BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), |
| null, true)) == null) { |
| Log.e(TAG, "Disconnected: error connecting to " + mDevice); |
| break; |
| } else { |
| transitionTo(mConnecting); |
| } |
| break; |
| case DISCONNECT: |
| Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); |
| break; |
| case CONNECTION_STATE_CHANGED: |
| int state = (int)message.obj; |
| Log.w(TAG, "connection state changed:" + state); |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| log("remote/wl connection, ensure csip is up as well"); |
| if (mNoCSIPReconn == false && mService != null && |
| mService.isLockSupportAvailable(mDevice)) { |
| /////*_CSIP |
| mCsipConnected = false; |
| mSetCoordinator.connect(mService.mCsipAppId, mDevice); |
| transitionTo(mConnecting); |
| break; |
| ////_CSIP*/ |
| } else { |
| transitionTo(mConnected); |
| } |
| } else { |
| Log.w(TAG, "Disconected: Connection failed to " + mDevice); |
| } |
| break; |
| case PSYNC_ACTIVE_TIMEOUT: |
| cancelActiveSync(null); |
| break; |
| default: |
| log("DISCONNECTED: not handled message:" + message.what); |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| @VisibleForTesting |
| class Connecting extends State { |
| @Override |
| public void enter() { |
| log( "Enter Connecting(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); |
| broadcastConnectionState(mDevice, mLastConnectionState, |
| BluetoothProfile.STATE_CONNECTING); |
| } |
| |
| @Override |
| public void exit() { |
| log("Exit Connecting(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| mLastConnectionState = BluetoothProfile.STATE_CONNECTING; |
| removeMessages(CONNECT_TIMEOUT); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| log("Connecting process message(" + mDevice + "): " + messageWhatToString( |
| message.what)); |
| |
| switch (message.what) { |
| case CONNECT: |
| log("Already Connecting to " + mDevice); |
| log("Ignore this connection request " + mDevice); |
| break; |
| case DISCONNECT: |
| Log.w(TAG, "Connecting: DISCONNECT deferred: " + mDevice); |
| deferMessage(message); |
| break; |
| case READ_BASS_CHARACTERISTICS: |
| Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); |
| deferMessage(message); |
| break; |
| case CSIP_CONNECTION_STATE_CHANGED: |
| int state = (int)message.obj; |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| ///*_CSIP |
| if (mCsipConnected == true) { |
| Log.e(TAG, "CSIP is already up, ignore this DUP event"); |
| break; |
| } |
| mCsipConnected = true; |
| Log.d(TAG, "Csip connected"); |
| transitionTo(mConnected); |
| } else { |
| Log.w(TAG, "CSIP Connection failed to " + mDevice); |
| if (mBluetoothGatt != null) { |
| //disc bass |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| } |
| transitionTo(mDisconnected); |
| } |
| |
| break; |
| case CONNECTION_STATE_CHANGED: |
| state = (int)message.obj; |
| Log.w(TAG, "Connecting: connection state changed:" + state); |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| if (mService != null && |
| mService.isLockSupportAvailable(mDevice)) { |
| ///*_CSIP |
| //If Lock support available & connect to csip |
| mCsipConnected = false; |
| mSetCoordinator.connect(mService.mCsipAppId, mDevice); |
| break; |
| //_CSIP*/ |
| } else { |
| transitionTo(mConnected); |
| } |
| } else { |
| Log.w(TAG, "Connection failed to " + mDevice); |
| transitionTo(mDisconnected); |
| } |
| break; |
| case CONNECT_TIMEOUT: |
| Log.w(TAG, "CONNECT_TIMEOUT"); |
| BluetoothDevice device = (BluetoothDevice) message.obj; |
| if (!mDevice.equals(device)) { |
| Log.e(TAG, "Unknown device timeout " + device); |
| break; |
| } |
| transitionTo(mDisconnected); |
| break; |
| case PSYNC_ACTIVE_TIMEOUT: |
| deferMessage(message); |
| break; |
| default: |
| log("CONNECTING: not handled message:" + message.what); |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| private byte[] reverseBytes(byte[] a) { |
| if (mNoReverse) { |
| log("no reverse is enabled>"); |
| return a; |
| } |
| for(int i=0; i<a.length/2; i++){ |
| byte tmp = a[i]; |
| a[i] = a[a.length -i -1]; |
| a[a.length -i -1] = tmp; |
| } |
| |
| return a; |
| } |
| |
| private byte[] BluetoothAddressToBytes (String s) { |
| log("BluetoothAddressToBytes: input string:" + s); |
| String[] splits = s.split(":"); |
| |
| byte[] addressBytes = new byte[6]; |
| for (int i=0; i<6; i++) { |
| int hexValue = Integer.parseInt(splits[i], 16); |
| log("hexValue:" + hexValue); |
| addressBytes[i] = (byte)hexValue; |
| } |
| |
| return addressBytes; |
| |
| } |
| |
| private int convertBisIndiciesToIntegerValue(List<BleBroadcastSourceChannel> bisIndicies, int subGroupId) { |
| int audioBisIndex = 0; |
| if (bisIndicies != null) { |
| for (int i=0; i<bisIndicies.size(); i++) { |
| if (bisIndicies.get(i).getStatus() == true && bisIndicies.get(i).getSubGroupId() == subGroupId) { |
| audioBisIndex = audioBisIndex | 1<<(bisIndicies.get(i).getIndex()-1); |
| log( "set the bit" + bisIndicies.get(i).getIndex() + "after:" + audioBisIndex); |
| } |
| } |
| } else { |
| log("bisIndicies Channels are null"); |
| audioBisIndex = 0xFFFFFFFF; |
| |
| } |
| log( "audioBisIndex" + audioBisIndex); |
| |
| return audioBisIndex; |
| } |
| |
| private byte[] convertSourceInfoToAddSourceByteArray(BleBroadcastSourceInfo srcInfo) { |
| byte[] res; |
| /*fixed length for add source op*/ |
| int addSourceFixedLength = 16; |
| byte[] metaDataLength = null; |
| BluetoothDevice broadcastSource = null; |
| String localBcastAddr = null; |
| BCService.PAResults paRes = null; |
| log("Get PAresults for :" + srcInfo.getSourceDevice()); |
| broadcastSource = srcInfo.getSourceDevice(); |
| |
| ///*_BMS |
| if (mPublicAddrForcSrc == false) { |
| if (isLocalBroadcastSource(broadcastSource)){ |
| //update broadcastSource if it is colocated |
| if (mBAService != null) { |
| localBcastAddr = mBAService.BroadcastGetAdvAddress(); |
| } |
| if (localBcastAddr == null) { |
| Log.w(TAG, "convertSourceInfoToAddSourceByteArray: localBCast not avaiable"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); |
| return null; |
| } else { |
| broadcastSource = |
| BluetoothAdapter.getDefaultAdapter().getRemoteDevice(localBcastAddr); |
| log("convertSourceInfoToAddSourceByteArray: colocated case: " + broadcastSource); |
| } |
| } |
| } |
| //_BMS*/ |
| paRes = mService.getPAResults(broadcastSource); |
| if (paRes == null) { |
| Log.e(TAG, "No mathcing psync, scan res for this addition"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); |
| return null; |
| } |
| |
| //populate metadata from BASE levelOne |
| BaseData base_ = mService.getBASE(paRes.mSyncHandle); |
| if (base_ == null) { |
| Log.e(TAG, "No valid base data populated for this device"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); |
| return null; |
| } |
| int numSubGroups = base_.getNumberOfSubgroupsofBIG(); |
| metaDataLength = new byte[numSubGroups]; |
| int totalMetadataLength = 0; |
| for (int i=0; i<numSubGroups; i++) { |
| if (base_.getMetadata(i) == null) { |
| Log.w(TAG, "no valid metadata from BASE"); |
| metaDataLength[i] = 0; |
| } else { |
| metaDataLength[i] = (byte)base_.getMetadata(i).length; |
| log("metaDataLength updated:" + metaDataLength[i]); |
| } |
| totalMetadataLength = totalMetadataLength + metaDataLength[i]; |
| } |
| res = new byte [addSourceFixedLength + numSubGroups*5 + totalMetadataLength]; |
| srcInfo.setSourceDevice(broadcastSource); |
| srcInfo.setAdvAddressType((byte)paRes.mAddressType); |
| srcInfo.setAdvertisingSid((byte)paRes.mAdvSid); |
| srcInfo.setBroadcasterId(paRes.mBroadcastId); |
| |
| if (isValidBroadcastSourceInfo(srcInfo) == false) { |
| log("Discarding this Add Broadcast source If It is DUP"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION); |
| return null; |
| } |
| |
| res[0] = BASS_ADD_SOURCE_OPCODE; |
| res[1] = (byte)paRes.mAddressType; |
| String address = broadcastSource.getAddress(); |
| byte[] addrByteVal = BluetoothAddressToBytes(address); |
| log("Address bytes: " + Arrays.toString(addrByteVal)); |
| byte[] revAddress= reverseBytes(addrByteVal); |
| log("reverse Address bytes: " + Arrays.toString(revAddress)); |
| System.arraycopy(revAddress, 0, res, 2, 6); |
| res[8] = (byte)paRes.mAdvSid; |
| |
| //System.arraycopy(paRes.mBroadcastId, 0, res, 9, BROADCAST_SOURCE_ID_LENGTH); |
| log("mBroadcastId: " + paRes.mBroadcastId); |
| res[9] = (byte)(paRes.mBroadcastId & 0x00000000000000FF); |
| res[10] = (byte)((paRes.mBroadcastId & 0x000000000000FF00) >>> 8); |
| res[11] = (byte)((paRes.mBroadcastId & 0x0000000000FF0000) >>> 16); |
| if (mDefNoPAS == false && |
| srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { |
| res[12] = (byte)(0x01); |
| } else { |
| log("setting PA sync to ZERO"); |
| res[12] = (byte)0x00; |
| } |
| |
| res[13] = (byte)(paRes.mPAInterval & 0x00000000000000FF); |
| res[14] = (byte)((paRes.mPAInterval & 0x000000000000FF00)>>>8); |
| |
| res[15] = base_.getNumberOfSubgroupsofBIG(); |
| |
| int offset = 16; |
| for (int i=0; i<base_.getNumberOfSubgroupsofBIG(); i++) { |
| |
| //Select based on PACs? |
| //int bisIndexValue = convertBisIndiciesToIntegerValue(srcInfo.getBroadcastChannelsSyncStatus()); |
| int bisIndexValue = convertBisIndiciesToIntegerValue(mService.getBassUtils().selectBises(mDevice, srcInfo, base_), i); |
| |
| res[offset++] = (byte)(bisIndexValue & 0x00000000000000FF); |
| res[offset++] = (byte)((bisIndexValue & 0x000000000000FF00)>>>8); |
| res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); |
| res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); |
| |
| res[offset++] = metaDataLength[i]; |
| if (metaDataLength[i] != 0) { |
| if (isLocalBroadcastSource(broadcastSource) == false) { |
| byte[] revMetadata = reverseBytes(base_.getMetadata(i)); |
| System.arraycopy(revMetadata, 0, res, offset, metaDataLength[i]); |
| } else { |
| System.arraycopy(base_.getMetadata(i), 0, res, offset, metaDataLength[i]); |
| } |
| } |
| offset = offset + metaDataLength[i]; |
| } |
| |
| log("ADD_BCAST_SOURCE in Bytes"); |
| BassUtils.printByteArray(res); |
| return res; |
| } |
| |
| private byte[] convertSourceInfoToUpdateSourceByteArray(BleBroadcastSourceInfo srcInfo) { |
| byte[] res; |
| int updateSourceFixedLength = 6; |
| BCService.PAResults paRes = null; |
| BleBroadcastSourceInfo existingSI = getBroadcastSourceInfoForSourceId(srcInfo.getSourceId()); |
| if (existingSI == null) { |
| log("no existing SI for update source op"); |
| return null; |
| } |
| |
| byte numSubGroups = existingSI.getNumberOfSubGroups(); |
| //on Modify source, dont update any Metadata |
| byte metaDataLength = 0; |
| res = new byte [updateSourceFixedLength + numSubGroups*5]; |
| |
| res[0] = BASS_UPDATE_SOURCE_OPCODE; |
| res[1] = srcInfo.getSourceId(); |
| |
| if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { |
| res[2] = (byte)(0x01); |
| } else { |
| res[2] = (byte)0x00; |
| } |
| //update these from existing SI |
| BluetoothDevice existingSrcDevice = existingSI.getSourceDevice(); |
| if (isAddedBroadcastSourceIsLocal(existingSrcDevice)) { |
| int paInterval = 0x0000FFFF; |
| paInterval = mBAService.BroadcastGetAdvInterval(); |
| res[4] = (byte)((paInterval & 0x000000000000FF00)>>>8); |
| res[3] = (byte)(paInterval & 0x00000000000000FF); |
| } else { |
| //for non-c mmodify op, set PA Interval as UNKNOWN |
| res[4] = (byte)0xFF; |
| res[3] = (byte)0xFF; |
| } |
| //For modify op, just set number of Subgroups as UNKNOWN |
| //ZERO is treated as UNKNOWN |
| res[5] = numSubGroups; |
| |
| int offset = 6; |
| int bisIndexValue = 0; |
| Map<Integer, Integer> bisIndexList = existingSI.getBisIndexList(); |
| if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { |
| //Force BIS index value to NO_PREF for modify SRC |
| bisIndexValue = 0xFFFFFFFF; |
| } else { |
| bisIndexValue = 0x00000000; |
| } |
| log("UPDATE_BCAST_SOURCE b4: bisIndexValue : " + bisIndexValue); |
| //If there is an empty List, set NO pref all subgroups |
| if (bisIndexList == null || bisIndexList.size() == 0) { |
| bisIndexValue = 0xFFFFFFFF; |
| } |
| for (int i=0; i<numSubGroups; i++) { |
| |
| //Select based on PACs? |
| //int bisIndexValue = convertBisIndiciesToIntegerValue(srcInfo.getBroadcastChannelsSyncStatus()); |
| if (bisIndexValue != 0xFFFFFFFF && bisIndexValue != 0) { |
| bisIndexValue = bisIndexList.get(i); |
| } |
| log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); |
| |
| res[offset++] = (byte)(bisIndexValue & 0x00000000000000FF); |
| res[offset++] = (byte)((bisIndexValue & 0x000000000000FF00)>>>8); |
| res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); |
| res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); |
| |
| res[offset++] = metaDataLength; |
| } |
| log("UPDATE_BCAST_SOURCE in Bytes"); |
| BassUtils.printByteArray(res); |
| return res; |
| } |
| |
| private byte[] convertAsciitoValues (byte[] val) { |
| byte[] ret = new byte[val.length]; |
| for (int i=0; i< val.length; i++) { |
| ret[i] = (byte)(val[i] - (byte)'0'); |
| } |
| log("convertAsciitoValues: returns:" + Arrays.toString(val)); |
| return ret; |
| } |
| |
| private byte[] convertSourceInfoToSetBroadcastCodeByteArray(BleBroadcastSourceInfo srcInfo) { |
| |
| byte[] res = new byte[PIN_CODE_CMD_LEN]; |
| res[0] = BASS_SET_BCAST_PIN_OPCODE; |
| res[1] = srcInfo.getSourceId(); |
| log("convertSourceInfoToSetBroadcastCodeByteArray: Source device : " + srcInfo.getSourceDevice()); |
| byte[] actualPIN = null; |
| //srcInfo.getSourceDevice() will be NULL if this request coming from SDK |
| // srcInfo.getSourceDevice() will have valid Source device only If this is |
| //collocated device |
| if (srcInfo.getSourceDevice() != null && |
| isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { |
| //colocated Source addition |
| //query the Encryption Key from BMS and update |
| ///*_BMS |
| actualPIN = mBAService.GetEncryptionKey(null); |
| //_BMS*/ |
| log("colocatedBcastCode is " + Arrays.toString(actualPIN)); |
| } else { |
| //Can Keep as ASCII as is |
| String reversePIN = new StringBuffer(srcInfo.getBroadcastCode()).reverse().toString(); |
| actualPIN = reversePIN.getBytes(); |
| } |
| if (actualPIN == null) { |
| Log.e(TAG, "actual PIN is null"); |
| return null; |
| } else { |
| log( "byte array broadcast Code:" + Arrays.toString(actualPIN)); |
| log( "pinLength:" + actualPIN.length); |
| |
| //Fill the PIN code in the Last Position |
| System.arraycopy(actualPIN, 0, res, ((PIN_CODE_CMD_LEN)-actualPIN.length), actualPIN.length); |
| |
| log("SET_BCAST_PIN in Bytes"); |
| BassUtils.printByteArray(res); |
| } |
| return res; |
| } |
| |
| private boolean IsItRightTimeToUpdateBroadcastPIN(byte srcId) { |
| Collection<BleBroadcastSourceInfo> srcInfos = mBleBroadcastSourceInfos.values(); |
| Iterator<BleBroadcastSourceInfo> iterator = srcInfos.iterator(); |
| boolean ret = false; |
| if (mForceSB) { |
| log("force SB is set"); |
| return true; |
| } |
| while (iterator.hasNext()) { |
| BleBroadcastSourceInfo sI = iterator.next(); |
| if (sI == null) { |
| log("src Info is null"); |
| continue; |
| } |
| if (srcId == sI.getSourceId() && |
| sI.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { |
| ret = true; |
| break; |
| } |
| } |
| log("IsItRightTimeToUpdateBroadcastPIN returning:" + ret); |
| return ret; |
| } |
| |
| @VisibleForTesting |
| class Connected extends State { |
| @Override |
| public void enter() { |
| log( "Enter Connected(" + mDevice + "): " |
| + messageWhatToString(getCurrentMessage().what)); |
| |
| removeDeferredMessages(CONNECT); |
| if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { |
| log("CONNECTED->CONNTECTED: Ignore"); |
| } else { |
| broadcastConnectionState(mDevice, mLastConnectionState, |
| BluetoothProfile.STATE_CONNECTED); |
| //initialize PACs for this devices |
| if (mPacsAvail == true) { |
| /* |
| PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); |
| if (mPacsClientService != null) { |
| log("trigger connect to Pacs"); |
| mPacsClientService.connect(mDevice); |
| } else { |
| Log.e(TAG, "PACs interface is null"); |
| } |
| */ |
| } |
| |
| ///*_VCP |
| if (mVcpForBroadcast) { |
| VcpController vcpController = VcpController.getVcpController(); |
| if (vcpController != null) { |
| log("trigger connect to Vcp Renderer"); |
| if (!vcpController.connect(mDevice, BluetoothVcp.MODE_BROADCAST)) { |
| log("Connect vcp failed"); |
| } |
| } else { |
| Log.e(TAG, "VcpController interface is null"); |
| } |
| } |
| //_VCP*/ |
| } |
| } |
| |
| @Override |
| public void exit() { |
| log("Exit Connected(" + mDevice + "): " |
| + messageWhatToString(getCurrentMessage().what)); |
| mLastConnectionState = BluetoothProfile.STATE_CONNECTED; |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| log("Connected process message(" + mDevice + "): " |
| + messageWhatToString(message.what)); |
| BleBroadcastSourceInfo srcInfo; |
| switch (message.what) { |
| case CONNECT: |
| Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); |
| break; |
| case DISCONNECT: |
| log("Disconnecting from " + mDevice); |
| if (mBluetoothGatt != null) { |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| //transitionTo(mDisconnecting); |
| cancelActiveSync(null); |
| //Trigger the CSip disconnection, dont worry about pass/failure |
| if (mCsipConnected && mSetCoordinator != null) { |
| mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); |
| mCsipConnected = false; |
| } |
| transitionTo(mDisconnected); |
| } else { |
| log("mBluetoothGatt is null"); |
| } |
| break; |
| case CONNECTION_STATE_CHANGED: |
| int state = (int)message.obj; |
| Log.w(TAG, "Connected:connection state changed:" + state); |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| Log.w(TAG, "device is already connected to Bass" + mDevice); |
| } else { |
| Log.w(TAG, "unexpected disconnected from " + mDevice); |
| cancelActiveSync(null); |
| ///*_CSIP |
| //Trigger the CSip disconnection, dont worry about pass/failure |
| if (mCsipConnected) { |
| mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); |
| mCsipConnected = false; |
| } |
| //_CSIP*/ |
| transitionTo(mDisconnected); |
| } |
| break; |
| case READ_BASS_CHARACTERISTICS: |
| BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic)message.obj; |
| if (mBluetoothGatt != null) { |
| mBluetoothGatt.readCharacteristic(characteristic); |
| transitionTo(mConnectedProcessing); |
| } else { |
| Log.e(TAG, "READ_BASS_CHARACTERISTICS is ignored, Gatt handle is null"); |
| } |
| break; |
| case START_SCAN_OFFLOAD: |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(REMOTE_SCAN_START); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| transitionTo(mConnectedProcessing); |
| } else { |
| log("no Bluetooth Gatt handle, may need to fetch write"); |
| } |
| break; |
| case STOP_SCAN_OFFLOAD: |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(REMOTE_SCAN_STOP); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| transitionTo(mConnectedProcessing); |
| } else { |
| log("no Bluetooth Gatt handle, may need to fetch write"); |
| } |
| break; |
| case SELECT_BCAST_SOURCE: |
| ScanResult scanRes = (ScanResult)message.obj; |
| boolean auto = ((int) message.arg1) == AUTO; |
| boolean isGroupOp = ((int) message.arg2) == GROUP_OP; |
| selectBroadcastSource(scanRes, isGroupOp, auto); |
| break; |
| case ADD_BCAST_SOURCE: |
| srcInfo = (BleBroadcastSourceInfo)message.obj; |
| log("Adding Broadcast source" + srcInfo); |
| byte[] addSourceInfo = convertSourceInfoToAddSourceByteArray(srcInfo); |
| if (addSourceInfo == null) { |
| Log.e(TAG, "add source: source Info is NULL"); |
| break; |
| } |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(addSourceInfo); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| transitionTo(mConnectedProcessing); |
| sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); |
| } else { |
| Log.e(TAG, "ADD_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); |
| sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); |
| } |
| break; |
| case UPDATE_BCAST_SOURCE: |
| srcInfo = (BleBroadcastSourceInfo)message.obj; |
| mAutoTriggerred = ((int) message.arg1) == AUTO; |
| log("Updating Broadcast source" + srcInfo); |
| byte[] updateSourceInfo = convertSourceInfoToUpdateSourceByteArray(srcInfo); |
| if (updateSourceInfo == null) { |
| Log.e(TAG, "update source: source Info is NULL"); |
| break; |
| } |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(updateSourceInfo); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| mPendingSourceId = srcInfo.getSourceId(); |
| transitionTo(mConnectedProcessing); |
| sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); |
| } else { |
| Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); |
| sendPendingCallbacks(UPDATE_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); |
| } |
| break; |
| case SET_BCAST_CODE: |
| srcInfo = (BleBroadcastSourceInfo)message.obj; |
| int cmdType = message.arg1; |
| log("SET_BCAST_CODE srcInfo: " + srcInfo); |
| |
| if (cmdType != QUEUED && |
| IsItRightTimeToUpdateBroadcastPIN(srcInfo.getSourceId()) != true) { |
| mSetBroadcastCodePending = true; |
| mSetBroadcastPINSrcInfo = srcInfo; |
| log("Ignore SET_BCAST now, but store it for later"); |
| //notify so that lock release happens as SET_BCAST_CODE |
| //queued for future |
| mService.notifyOperationCompletion(mDevice,SET_BCAST_CODE); |
| } else { |
| byte[] setBroadcastPINcmd = convertSourceInfoToSetBroadcastCodeByteArray(srcInfo); |
| if (setBroadcastPINcmd == null) { |
| Log.e(TAG, "SET_BCAST_CODE: Broadcast code is NULL"); |
| break; |
| } |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(setBroadcastPINcmd); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| mPendingSourceId = srcInfo.getSourceId(); |
| transitionTo(mConnectedProcessing); |
| sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); |
| } else { |
| Log.e(TAG, "SET_BCAST_CODE: no Bluetooth Gatt handle, Fatal"); |
| sendPendingCallbacks(SET_BCAST_CODE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); |
| |
| } |
| } |
| break; |
| case REMOVE_BCAST_SOURCE: |
| byte sourceId = (byte)message.arg1; |
| BluetoothDevice audioSrc = (BluetoothDevice)message.obj; |
| log("Removing Broadcast source: audioSource:" + audioSrc + "sourceId:" + sourceId); |
| byte[] removeSourceInfo = new byte [2]; |
| removeSourceInfo[0] = BASS_REMOVE_SOURCE_OPCODE; |
| removeSourceInfo[1] = sourceId; |
| if (mBluetoothGatt != null && |
| mBroadcastScanControlPoint != null) { |
| mBroadcastScanControlPoint.setValue(removeSourceInfo); |
| mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); |
| mPendingOperation = message.what; |
| mPendingSourceId = sourceId; |
| transitionTo(mConnectedProcessing); |
| sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); |
| } else { |
| Log.e(TAG, "REMOVE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); |
| sendPendingCallbacks(REMOVE_BCAST_SOURCE,INVALID_SRC_ID, |
| BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); |
| |
| } |
| break; |
| case PSYNC_ACTIVE_TIMEOUT: |
| cancelActiveSync(null); |
| break; |
| default: |
| log("CONNECTED: not handled message:" + message.what); |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| |
| void sendPendingCallbacks(int pendingOp, byte sourceId, int status) { |
| if (status != 0) { |
| //Notify service only In case of failure cases |
| //Success case would have been notified through State machine anyways |
| mService.notifyOperationCompletion(mDevice, pendingOp); |
| } |
| switch (pendingOp) { |
| case START_SCAN_OFFLOAD: |
| if (status != 0) { |
| if (mAutoTriggerred == false) { |
| log("notify the app only if start Scan offload fails"); |
| //shouldnt happen in general |
| mService.sendBroadcastSourceSelectedCallback(mDevice, null, status); |
| cancelActiveSync(null); |
| } else { |
| mAutoTriggerred = false; |
| } |
| } |
| break; |
| case ADD_BCAST_SOURCE: |
| if (status != 0) { |
| sourceId = INVALID_SRC_ID; |
| cancelActiveSync(null); |
| //stop Scan offload for colocated case |
| mService.stopScanOffloadInternal(mDevice, false); |
| } |
| mService.sendAddBroadcastSourceCallback(mDevice, sourceId, status); |
| break; |
| case UPDATE_BCAST_SOURCE: |
| if (mAutoTriggerred == false) { |
| mService.sendUpdateBroadcastSourceCallback(mDevice, sourceId, status); |
| } else { |
| mAutoTriggerred = false; |
| } |
| break; |
| case REMOVE_BCAST_SOURCE: |
| mService.sendRemoveBroadcastSourceCallback(mDevice, sourceId, status); |
| break; |
| case SET_BCAST_CODE: |
| mService.sendSetBroadcastPINupdatedCallback(mDevice, sourceId, status); |
| break; |
| default: |
| { |
| log("sendPendingCallbacks: unhandled case"); |
| } |
| } |
| } |
| @VisibleForTesting |
| class ConnectedProcessing extends State { |
| @Override |
| public void enter() { |
| log( "Enter ConnectedProcessing(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| } |
| |
| @Override |
| public void exit() { |
| log("Exit ConnectedProcessing(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| } |
| @Override |
| public boolean processMessage(Message message) { |
| log("ConnectedProcessing process message(" + mDevice + "): " + messageWhatToString( |
| message.what)); |
| switch (message.what) { |
| case CONNECT: |
| Log.w(TAG, "CONNECT request is ignored" + mDevice); |
| break; |
| case DISCONNECT: |
| Log.w(TAG, "DISCONNECT requested!: " + mDevice); |
| if (mBluetoothGatt != null) { |
| mBluetoothGatt.disconnect(); |
| mBluetoothGatt.close(); |
| mBluetoothGatt = null; |
| cancelActiveSync(null); |
| //Trigger the CSIP disconnection, dont worry about pass/failure |
| if (mCsipConnected && mSetCoordinator != null) { |
| mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); |
| mCsipConnected = false; |
| } |
| transitionTo(mDisconnected); |
| } else { |
| log("mBluetoothGatt is null"); |
| } |
| break; |
| case READ_BASS_CHARACTERISTICS: |
| Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); |
| deferMessage(message); |
| break; |
| case CONNECTION_STATE_CHANGED: |
| int state = (int)message.obj; |
| Log.w(TAG, "ConnectedProcessing: connection state changed:" + state); |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| Log.w(TAG, "should never happen from this state"); |
| } else { |
| Log.w(TAG, "Unexpected disconnection " + mDevice); |
| transitionTo(mDisconnected); |
| } |
| break; |
| case GATT_TXN_PROCESSED: |
| removeMessages(GATT_TXN_TIMEOUT); |
| int status = (int)message.arg1; |
| log( "GATT transaction processed for" + mDevice); |
| mService.notifyOperationCompletion(mDevice, mPendingOperation); |
| if (status == BluetoothGatt.GATT_SUCCESS) { |
| if (mPendingOperation == SET_BCAST_CODE) { |
| //If Pending operation is SET_BCAST_CODE |
| //send callback to notify BCAST is updated |
| //This is needed only for SET_BCAST operation |
| sendPendingCallbacks(mPendingOperation, |
| mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); |
| } |
| } else { |
| //any failure to write operation |
| //will be converted to corresponding |
| //callback with failure status |
| sendPendingCallbacks(mPendingOperation, |
| mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_FAILURE); |
| } |
| transitionTo(mConnected); |
| break; |
| case GATT_TXN_TIMEOUT: |
| log( "GATT transaction timedout for" + mDevice); |
| mService.notifyOperationCompletion(mDevice, mPendingOperation); |
| sendPendingCallbacks(mPendingOperation, |
| mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_TXN_TIMEOUT); |
| mPendingOperation = -1; |
| transitionTo(mConnected); |
| mPendingSourceId = -1; |
| break; |
| case START_SCAN_OFFLOAD: |
| case STOP_SCAN_OFFLOAD: |
| case SELECT_BCAST_SOURCE: |
| case ADD_BCAST_SOURCE: |
| case SET_BCAST_CODE: |
| case REMOVE_BCAST_SOURCE: |
| case PSYNC_ACTIVE_TIMEOUT: |
| log("defer the message:" + message.what + "so that it will be processed later"); |
| deferMessage(message); |
| break; |
| default: |
| log("CONNECTEDPROCESSING: not handled message:" + message.what); |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| |
| @VisibleForTesting |
| class Disconnecting extends State { |
| @Override |
| public void enter() { |
| log( "Enter Disconnecting(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); |
| broadcastConnectionState(mDevice, mLastConnectionState, |
| BluetoothProfile.STATE_DISCONNECTING); |
| } |
| @Override |
| public void exit() { |
| log("Exit Disconnecting(" + mDevice + "): " + messageWhatToString( |
| getCurrentMessage().what)); |
| removeMessages(CONNECT_TIMEOUT); |
| mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; |
| } |
| @Override |
| public boolean processMessage(Message message) { |
| log("Disconnecting process message(" + mDevice + "): " + messageWhatToString( |
| message.what)); |
| switch (message.what) { |
| case CONNECT: |
| log("Disconnecting to " + mDevice); |
| log("deferring this connection request " + mDevice); |
| deferMessage(message); |
| break; |
| case DISCONNECT: |
| Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice); |
| break; |
| case CONNECTION_STATE_CHANGED: |
| int state = (int)message.obj; |
| Log.w(TAG, "Disconnecting: connection state changed:" + state); |
| if (state == BluetoothProfile.STATE_CONNECTED) { |
| Log.e(TAG, "should never happen from this state"); |
| transitionTo(mConnected); |
| } else { |
| Log.w(TAG, "disconnection successfull to " + mDevice); |
| cancelActiveSync(null); |
| transitionTo(mDisconnected); |
| ///*_CSIP |
| //Trigger the CSip disconnection, dont worry about pass/failure |
| if (mCsipConnected) { |
| mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); |
| mCsipConnected = false; |
| } |
| //_CSIP*/ |
| } |
| break; |
| case CONNECT_TIMEOUT: |
| Log.w(TAG, "CONNECT_TIMEOUT"); |
| |
| BluetoothDevice device = (BluetoothDevice) message.obj; |
| if (!mDevice.equals(device)) { |
| Log.e(TAG, "Unknown device timeout " + device); |
| break; |
| } |
| transitionTo(mDisconnected); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| |
| void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { |
| log( "broadcastConnectionState " + device + ": " + fromState + "->" + toState); |
| if (fromState == BluetoothProfile.STATE_CONNECTED && |
| toState == BluetoothProfile.STATE_CONNECTED) { |
| log("CONNECTED->CONNTECTED: Ignore"); |
| return; |
| } |
| Intent intent = new Intent(BluetoothSyncHelper.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| mService.sendBroadcastAsUser(intent, UserHandle.ALL, |
| BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); |
| } |
| |
| int getConnectionState() { |
| String currentState = "Unknown"; |
| if (getCurrentState() != null) { |
| currentState = getCurrentState().getName(); |
| } |
| switch (currentState) { |
| case "Disconnected": |
| log("Disconnected"); |
| return BluetoothProfile.STATE_DISCONNECTED; |
| case "Disconnecting": |
| log("Disconnecting"); |
| return BluetoothProfile.STATE_DISCONNECTING; |
| case "Connecting": |
| log("Connecting"); |
| return BluetoothProfile.STATE_CONNECTING; |
| case "Connected": |
| case "ConnectedProcessing": |
| log("connected"); |
| return BluetoothProfile.STATE_CONNECTED; |
| default: |
| Log.e(TAG, "Bad currentState: " + currentState); |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| } |
| |
| BluetoothDevice getDevice() { |
| return mDevice; |
| } |
| |
| synchronized boolean isConnected() { |
| return getCurrentState() == mConnected; |
| } |
| |
| public static String messageWhatToString(int what) { |
| switch (what) { |
| case CONNECT: |
| return "CONNECT"; |
| case DISCONNECT: |
| return "DISCONNECT"; |
| case CONNECTION_STATE_CHANGED: |
| return "CONNECTION_STATE_CHANGED"; |
| case GATT_TXN_PROCESSED: |
| return "GATT_TXN_PROCESSED"; |
| case READ_BASS_CHARACTERISTICS: |
| return "READ_BASS_CHARACTERISTICS"; |
| case START_SCAN_OFFLOAD: |
| return "START_SCAN_OFFLOAD"; |
| case STOP_SCAN_OFFLOAD: |
| return "STOP_SCAN_OFFLOAD"; |
| case ADD_BCAST_SOURCE: |
| return "ADD_BCAST_SOURCE"; |
| case SELECT_BCAST_SOURCE: |
| return "SELECT_BCAST_SOURCE"; |
| case UPDATE_BCAST_SOURCE: |
| return "UPDATE_BCAST_SOURCE"; |
| case SET_BCAST_CODE: |
| return "SET_BCAST_CODE"; |
| case REMOVE_BCAST_SOURCE: |
| return "REMOVE_BCAST_SOURCE"; |
| case PSYNC_ACTIVE_TIMEOUT: |
| return "PSYNC_ACTIVE_TIMEOUT"; |
| case CSIP_CONNECTION_STATE_CHANGED: |
| return "CSIP_CONNECTION_STATE_CHANGED"; |
| case CONNECT_TIMEOUT: |
| return "CONNECT_TIMEOUT"; |
| default: |
| break; |
| } |
| return Integer.toString(what); |
| } |
| |
| private static String profileStateToString(int state) { |
| switch (state) { |
| case BluetoothProfile.STATE_DISCONNECTED: |
| return "DISCONNECTED"; |
| case BluetoothProfile.STATE_CONNECTING: |
| return "CONNECTING"; |
| case BluetoothProfile.STATE_CONNECTED: |
| return "CONNECTED"; |
| case BluetoothProfile.STATE_DISCONNECTING: |
| return "DISCONNECTING"; |
| default: |
| break; |
| } |
| return Integer.toString(state); |
| } |
| |
| public void dump(StringBuilder sb) { |
| ProfileService.println(sb, "mDevice: " + mDevice); |
| ProfileService.println(sb, " StateMachine: " + this); |
| // Dump the state machine logs |
| StringWriter stringWriter = new StringWriter(); |
| PrintWriter printWriter = new PrintWriter(stringWriter); |
| super.dump(new FileDescriptor(), printWriter, new String[]{}); |
| printWriter.flush(); |
| stringWriter.flush(); |
| ProfileService.println(sb, " StateMachineLog:"); |
| Scanner scanner = new Scanner(stringWriter.toString()); |
| while (scanner.hasNextLine()) { |
| String line = scanner.nextLine(); |
| ProfileService.println(sb, " " + line); |
| } |
| scanner.close(); |
| } |
| |
| @Override |
| protected void log( String msg) { |
| if (BASS_DBG) { |
| super.log(msg); |
| } |
| } |
| |
| private static void logByteArray(String prefix, byte[] value, int offset, int count) { |
| StringBuilder builder = new StringBuilder(prefix); |
| for (int i = offset; i < count; i++) { |
| builder.append(String.format("0x%02X", value[i])); |
| if (i != value.length - 1) { |
| builder.append(", "); |
| } |
| } |
| Log.d(TAG, builder.toString()); |
| } |
| } |