| /****************************************************************************** |
| * |
| * Copyright 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| ******************************************************************************/ |
| |
| |
| package com.android.bluetooth.acm; |
| |
| import static android.Manifest.permission.BLUETOOTH_CONNECT; |
| |
| import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; |
| |
| import android.bluetooth.BluetoothA2dp; |
| import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus; |
| import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus; |
| import android.bluetooth.BluetoothCodecConfig; |
| import android.bluetooth.BluetoothCodecStatus; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioManager; |
| import android.os.HandlerThread; |
| import android.os.Handler; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.ParcelUuid; |
| import android.util.Log; |
| import android.os.Message; |
| import android.bluetooth.BluetoothGroupCallback; |
| import com.android.bluetooth.groupclient.GroupService; |
| import android.bluetooth.DeviceGroup; |
| import android.bluetooth.BluetoothDeviceGroup; |
| import com.android.bluetooth.apm.ActiveDeviceManagerService; |
| import com.android.bluetooth.apm.VolumeManager; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| import android.os.SystemProperties; |
| import com.android.bluetooth.BluetoothMetricsProto; |
| import com.android.bluetooth.BluetoothStatsLog; |
| import com.android.bluetooth.Utils; |
| import android.bluetooth.BluetoothAdapter; |
| import com.android.bluetooth.apm.ApmConst; |
| import com.android.bluetooth.btservice.AdapterService; |
| import com.android.bluetooth.btservice.MetricsLogger; |
| import com.android.bluetooth.btservice.ProfileService; |
| import com.android.bluetooth.btservice.ServiceFactory; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.ArrayUtils; |
| import java.util.Iterator; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.HashMap; |
| import java.util.Objects; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| import com.android.bluetooth.vcp.VcpController; |
| /** |
| * Provides Bluetooth ACM profile, as a service in the Bluetooth application. |
| * @hide |
| */ |
| public class AcmService extends ProfileService { |
| private static final boolean DBG = true; |
| private static final String TAG = "AcmService"; |
| private String mAcmName; |
| public static final int ACM_AUDIO_UNICAST = 25; |
| public static final int INVALID_SET_ID = 0x10; |
| private static AcmService sAcmService; |
| private BluetoothAdapter mAdapter; |
| private AdapterService mAdapterService; |
| private HandlerThread mStateMachinesThread; |
| private static final int LOCK_RELEASED = 0; // (LOCK Released successfully) |
| private static final int LOCK_RELEASED_TIMEOUT = 1; // (LOCK Released by timeout) |
| private static final int ALL_LOCKS_ACQUIRED = 2; // (LOCK Acquired for all requested set members) |
| private static final int SOME_LOCKS_ACQUIRED_REASON_TIMEOUT = 3; // (Request timeout for some set members) |
| private static final int SOME_LOCKS_ACQUIRED_REASON_DISC = 4; // (Some of the set members were disconnected) |
| private static final int LOCK_DENIED = 5; // (Denied by one of the set members) |
| private static final int INVALID_REQUEST_PARAMS = 6; // (Upper layer provided invalid parameters) |
| private static final int LOCK_RELEASE_NOT_ALLOWED = 7; // (Response from remote (PTS)) |
| private static final int INVALID_VALUE = 8; |
| @VisibleForTesting |
| AcmNativeInterface mAcmNativeInterface; |
| @VisibleForTesting |
| ServiceFactory mFactory = new ServiceFactory(); |
| |
| static final int CONTEXT_TYPE_UNKNOWN = 0; |
| static final int CONTEXT_TYPE_MUSIC = 1; |
| static final int CONTEXT_TYPE_VOICE = 2; |
| static final int CONTEXT_TYPE_MUSIC_VOICE = 3; |
| static final int CONTEXT_TYPE_BROADCAST_AUDIO = 6; |
| |
| private AcmCodecConfig mAcmCodecConfig; |
| private final Object mAudioManagerLock = new Object(); |
| private final Object mBtLeaLock = new Object(); |
| private final Object mBtAcmLock = new Object(); |
| private String mLeaChannelMode = "stereo"; |
| private AudioManager mAudioManager; |
| @GuardedBy("mStateMachines") |
| private BluetoothDevice mGroupBdAddress = null; |
| private BluetoothDevice mActiveDevice = null; |
| private BluetoothDevice mActiveDeviceVoice = null; |
| private int mActiveDeviceProfile = 0; |
| private int mActiveDeviceVoiceProfile = 0; |
| private final ConcurrentMap<BluetoothDevice, AcmStateMachine> mStateMachines = |
| new ConcurrentHashMap<>(); |
| private HashMap<BluetoothDevice, BluetoothAcmDevice> mAcmDevices = |
| new HashMap<BluetoothDevice, BluetoothAcmDevice>(); |
| |
| // Upper limit of all ACM devices: Bonded or Connected |
| private static final int MAX_ACM_STATE_MACHINES = 50; |
| // Upper limit of all ACM devices that are Connected or Connecting |
| private int mMaxConnectedAudioDevices = 1; |
| CsipManager mCsipManager = null; |
| boolean mIsCsipRegistered = false; |
| boolean mShoPend = false; |
| boolean mVoiceShoPend = false; |
| //volume |
| private int mAudioStreamMax; |
| private int mActiveDeviceLocalMediaVol; |
| private int mActiveDeviceLocalVoiceVol; |
| private boolean mActiveDeviceIsMuted; |
| private static final int VCP_MAX_VOL = 255; |
| private VcpController mVcpController; |
| |
| private BroadcastReceiver mBondStateChangedReceiver; |
| private final ReentrantReadWriteLock mAcmNativeInterfaceLock = new ReentrantReadWriteLock(); |
| public int mCsipAppId = -1; |
| |
| private static final int SET_EBMONO_CFG = 1; |
| private static final int SET_EBSTEREO_CFG = 2; |
| private static final int MonoCfg_Timeout = 3000; |
| private static final int StereoCfg_Timeout = 3000; |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) |
| { |
| synchronized(mBtLeaLock) { |
| switch (msg.what) { |
| case SET_EBMONO_CFG: |
| Log.d(TAG, "setparameters to Mono"); |
| synchronized (mAudioManagerLock) { |
| if(mAudioManager != null) |
| mAudioManager.setParameters("LEAMono=true"); |
| } |
| mLeaChannelMode = "mono"; |
| break; |
| case SET_EBSTEREO_CFG: |
| Log.d(TAG, "setparameters to stereo"); |
| synchronized (mAudioManagerLock) { |
| if(mAudioManager != null) |
| mAudioManager.setParameters("LEAMono=false"); |
| } |
| mLeaChannelMode = "stereo"; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| }; |
| |
| @Override |
| protected void create() { |
| Log.i(TAG, "create()"); |
| } |
| |
| @Override |
| protected boolean start() { |
| Log.i(TAG, "start()"); |
| String propValue; |
| |
| if (sAcmService != null) { |
| Log.w(TAG, "AcmService is already running"); |
| return true; |
| } |
| |
| // Step 1: Get AdapterService, AcmNativeInterface. |
| // None of them can be null. |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), |
| "AdapterService cannot be null when AcmService starts"); |
| mAcmNativeInterface = Objects.requireNonNull(AcmNativeInterface.getInstance(), |
| "AcmNativeInterface cannot be null when AcmService starts"); |
| |
| mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), |
| "AdapterService cannot be null when StreamAudioService starts"); |
| |
| Log.i(TAG, "mAdapterService.isHostAdvAudioUnicastFeatureSupported() returned " |
| + mAdapterService.isHostAdvAudioUnicastFeatureSupported()); |
| Log.i(TAG, "mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() returned " |
| + mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported()); |
| Log.i(TAG, "mAdapterService.isAdvUnicastAudioFeatEnabled() returned " |
| + mAdapterService.isAdvUnicastAudioFeatEnabled()); |
| |
| // SOC supports unicast, host supports unicast and stereo recording |
| if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && |
| mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() && |
| mAdapterService.isAdvUnicastAudioFeatEnabled()) { |
| |
| Log.i(TAG, "SOC supports unicast, host supports unicast, stereo recording"); |
| // set properties only if they are not set to allow user enable/disable |
| // the features explicitly |
| propValue = SystemProperties.get("persist.vendor.service.bt.bap.enable_ucast"); |
| |
| if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { |
| SystemProperties.set("persist.vendor.service.bt.bap.enable_ucast", "true"); |
| } else { |
| Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " |
| + propValue); |
| } |
| |
| propValue = SystemProperties.get("persist.vendor.service.bt.recording_supported"); |
| if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { |
| SystemProperties.set("persist.vendor.service.bt.recording_supported", "true"); |
| Log.i(TAG, "persist.vendor.service.bt.recording_supported set to true"); |
| } else { |
| Log.i(TAG, "persist.vendor.service.bt.recording_supported is already set to " |
| + propValue); |
| } |
| } |
| |
| Log.i(TAG, "mAdapterService.isHostQHSFeatureSupported() returned " |
| + mAdapterService.isHostQHSFeatureSupported()); |
| |
| // SOC supports unicast, host supports unicast and QHS |
| if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && |
| mAdapterService.isHostQHSFeatureSupported() && |
| mAdapterService.isAdvUnicastAudioFeatEnabled()) { |
| |
| Log.i(TAG, "SOC supports unicast, host supports unicast, QHS"); |
| // set properties only if they are not set to allow user enable/disable |
| // the features explicitly |
| propValue = SystemProperties.get("persist.vendor.btstack.qhs_enable"); |
| |
| if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { |
| SystemProperties.set("persist.vendor.btstack.qhs_enable", "true"); |
| } else { |
| Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " |
| + propValue); |
| } |
| } |
| |
| Log.i(TAG, "isHostAdvAudioLC3QFeatureSupported(): " |
| + mAdapterService.isHostAdvAudioLC3QFeatureSupported()); |
| |
| // SOC supports unicast, host supports unicast and LC3Q |
| if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && |
| mAdapterService.isHostAdvAudioLC3QFeatureSupported() && |
| mAdapterService.isAdvUnicastAudioFeatEnabled()) { |
| |
| Log.i(TAG, "host supports LC3Q"); |
| // set properties only if they are not set to allow user enable/disable |
| // the features explicitly |
| propValue = SystemProperties.get("persist.vendor.service.bt.is_lc3q_supported"); |
| |
| if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { |
| SystemProperties.set("persist.vendor.service.bt.is_lc3q_supported", "true"); |
| } else { |
| Log.i(TAG, "persist.vendor.service.bt.is_lc3q_supported is already set to " |
| + propValue); |
| } |
| } |
| |
| // Step 2: Get maximum number of connected audio devices |
| mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); |
| Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); |
| |
| |
| String LeaChannelMode = SystemProperties.get("persist.vendor.btstack.Lea.defaultchannelmode"); |
| if (!LeaChannelMode.isEmpty() && "mono".equals(LeaChannelMode)) { |
| mLeaChannelMode = "mono"; |
| } |
| Log.d(TAG, "Default LEA ChannelMode: " + LeaChannelMode); |
| // Step 3: Start handler thread for state machines |
| mStateMachines.clear(); |
| mStateMachinesThread = new HandlerThread("AcmService.StateMachines"); |
| mStateMachinesThread.start(); |
| |
| // Step 4: Setup codec config |
| mAcmCodecConfig = new AcmCodecConfig(this, mAcmNativeInterface); |
| |
| if (mAdapterService.isAdvUnicastAudioFeatEnabled()) { |
| Log.d(TAG, "Initialize AcmNativeInterface"); |
| // Step 5: Initialize native interface |
| mAcmNativeInterface.init(mMaxConnectedAudioDevices, |
| mAcmCodecConfig.codecConfigPriorities()); |
| } |
| |
| // Step 6: Setup broadcast receivers |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); |
| mBondStateChangedReceiver = new BondStateChangedReceiver(); |
| registerReceiver(mBondStateChangedReceiver, filter); |
| synchronized (mAudioManagerLock) { |
| mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); |
| Objects.requireNonNull(mAudioManager, |
| "AudioManager cannot be null when AcmService starts"); |
| mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); |
| } |
| // Step 7: Mark service as started |
| setAcmService(this); |
| |
| //step 8: Register CSIP module |
| mCsipManager = new CsipManager(); |
| |
| //step 9: Get Vcp Controller |
| mVcpController = VcpController.make(this); |
| Objects.requireNonNull(mVcpController, "mVcpController cannot be null when AcmService starts"); |
| return true; |
| } |
| |
| @Override |
| protected boolean stop() { |
| Log.i(TAG, "stop()"); |
| if (sAcmService == null) { |
| Log.w(TAG, "stop() called before start()"); |
| return true; |
| } |
| |
| // Step 9: do quit Vcp Controller |
| if (mVcpController != null) { |
| mVcpController.doQuit(); |
| } |
| |
| // Step 8: Mark service as stopped |
| setAcmService(null); |
| |
| unregisterReceiver(mBondStateChangedReceiver); |
| mBondStateChangedReceiver = null; |
| // Step 6: Cleanup native interface |
| mAcmNativeInterface.cleanup(); |
| mAcmNativeInterface = null; |
| |
| // Step 5: Clear codec config |
| mAcmCodecConfig = null; |
| |
| // Step 4: Destroy state machines and stop handler thread |
| synchronized (mStateMachines) { |
| for (AcmStateMachine sm : mStateMachines.values()) { |
| sm.doQuit(); |
| sm.cleanup(); |
| } |
| mStateMachines.clear(); |
| } |
| mStateMachinesThread.quitSafely(); |
| mStateMachinesThread = null; |
| |
| // Step 2: Reset maximum number of connected audio devices |
| mMaxConnectedAudioDevices = 1; |
| |
| // Step 1: Clear AdapterService, AcmNativeInterface, AudioManager |
| mAcmNativeInterface = null; |
| mAdapterService = null; |
| if (mAcmDevices != null) |
| mAcmDevices.clear(); |
| |
| mCsipManager.unregisterCsip(); |
| mCsipManager = null; |
| return true; |
| } |
| |
| @Override |
| protected void cleanup() { |
| Log.i(TAG, "cleanup()"); |
| } |
| |
| @Override |
| protected IProfileServiceBinder initBinder() { |
| return new AcmBinder(this); |
| } |
| |
| private class BluetoothAcmDevice { |
| private BluetoothDevice mGrpDevice; // group bd address |
| private int mState; |
| private int msetID; |
| |
| BluetoothAcmDevice(BluetoothDevice device, int state, int setID) { |
| mGrpDevice = device; |
| mState = state; |
| msetID = setID; |
| } |
| } |
| |
| private BluetoothDevice getAddressFromString(String address) { |
| return mAdapter.getRemoteDevice(address); |
| } |
| |
| public BluetoothDevice makeGroupBdAddress(BluetoothDevice device, int state, int setid) { |
| Log.i(TAG, " Set id : " + setid + " Num of connected acm devices: " + mAcmDevices.size()); |
| boolean setIdMatched = false; |
| if (setid == INVALID_SET_ID) { |
| Log.d(TAG, "Device is not part of any group"); |
| BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(device, state, setid); |
| mAcmDevices.put(device, acmDevice); |
| mGroupBdAddress = acmDevice.mGrpDevice; |
| return mGroupBdAddress; |
| } |
| // BluetoothDevice bdaddr = null; |
| if (mAcmDevices == null) { |
| Log.d(TAG, "Hash Map is NULL"); |
| return mGroupBdAddress; |
| } |
| if (mAcmDevices.containsKey(device)) { |
| Log.d(TAG, "Device is available in Hash Map"); |
| BluetoothAcmDevice acmDevice = mAcmDevices.get(device); |
| mGroupBdAddress = acmDevice.mGrpDevice; |
| return mGroupBdAddress; |
| } |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if (d.msetID == setid) { |
| setIdMatched = true; |
| Log.d(TAG, "Device is part of same set ID"); |
| BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(d.mGrpDevice, state, setid); |
| mAcmDevices.put(device, acmDevice); |
| mGroupBdAddress = acmDevice.mGrpDevice; |
| break; |
| } |
| } |
| } |
| if (!setIdMatched) { |
| Log.d(TAG, "create new group or device is not part of existing set ID"); |
| String address = "9E:8B:00:00:00:0"; |
| BluetoothDevice bdaddr = getAddressFromString(address + setid); |
| BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(bdaddr, state, setid); |
| mAcmDevices.put(device, acmDevice); |
| mGroupBdAddress = bdaddr; |
| } |
| return mGroupBdAddress; |
| } |
| |
| public void handleAcmDeviceStateChange(BluetoothDevice device, int state, int setid) { |
| Log.d(TAG, "handleAcmDeviceStateChange: device: " + device + ", state: " + state |
| + " Set id : " + setid); |
| Log.i(TAG, " Num of connected ACM devices: " + mAcmDevices.size()); |
| boolean update = false; |
| if (device == null || mAcmDevices.size() == 0) |
| return; |
| BluetoothAcmDevice acmDevice = mAcmDevices.get(device); |
| //check if current active group address is same as this device group address |
| if (acmDevice != null && mGroupBdAddress != acmDevice.mGrpDevice) { |
| Log.d(TAG, "Inactive device is disconnected"); |
| update = true; |
| } |
| if (state == BluetoothProfile.STATE_DISCONNECTED) { |
| Log.d(TAG, "Remove Device from hash map"); |
| mAcmDevices.remove(device); |
| } else { |
| acmDevice.mState = state; |
| Log.d(TAG, "Update state"); |
| } |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if (d.msetID == setid) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| update = true; |
| Log.d(TAG, "Atleast one member is connected"); |
| break; |
| } |
| } |
| } |
| if (!update) { |
| /*if (!mAcmNativeInterface.setActiveDevice(null, 0)) {//send unknown context type |
| Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native layer"); |
| }*/ |
| } |
| } |
| |
| public static synchronized AcmService getAcmService() { |
| if (sAcmService == null) { |
| Log.w(TAG, "getAcmService(): service is null"); |
| return null; |
| } |
| if (!sAcmService.isAvailable()) { |
| Log.w(TAG, "getAcmService(): service is not available"); |
| return null; |
| } |
| return sAcmService; |
| } |
| |
| private static synchronized void setAcmService(AcmService instance) { |
| if (DBG) { |
| Log.d(TAG, "setAcmService(): set to: " + instance); |
| } |
| sAcmService = instance; |
| } |
| |
| public boolean connect(BluetoothDevice device, int contextType, |
| int profileType, int preferredContext) { |
| |
| if (DBG) { |
| Log.d(TAG, "connect(): " + device + " contextType: " + contextType |
| + " profileType: " + profileType + " preferredContext: " + preferredContext); |
| } |
| if (device.getAddress().contains("9E:8B:00:00:00")) { |
| Log.d(TAG, "Connect request for group"); |
| byte[] addrByte = Utils.getByteAddress(device); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> d = mCsipManager.getSetMembers(set_id); |
| if (d == null) { |
| Log.d(TAG, "No set member found"); |
| return false; |
| } |
| Iterator<BluetoothDevice> members = d.iterator(); |
| if (members != null) { |
| while (members.hasNext()) { |
| BluetoothDevice addr = members.next(); |
| Log.d(TAG, "connect member: " + addr); |
| synchronized (mStateMachines) { |
| if (!connectionAllowedCheckMaxDevices(addr)) { |
| // when mMaxConnectedAudioDevices is one, disconnect current device first. |
| if (mMaxConnectedAudioDevices == 1) { |
| List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( |
| new int[] {BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTING}); |
| for (BluetoothDevice sink : sinks) { |
| if (sink.equals(addr)) { |
| Log.w(TAG, "Connecting to device " + addr + " : disconnect skipped"); |
| continue; |
| } |
| disconnect(sink, contextType); |
| } |
| } else { |
| Log.e(TAG, "Cannot connect to " + addr + " : too many connected devices"); |
| return false; |
| } |
| } |
| AcmStateMachine smConnect = getOrCreateStateMachine(addr); |
| if (smConnect == null) { |
| Log.e(TAG, "Cannot connect to " + addr + " : no state machine"); |
| return false; |
| } |
| Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); |
| msg.obj = preferredContext; |
| msg.arg1 = contextType; |
| msg.arg2 = profileType; |
| smConnect.sendMessage(msg); |
| } |
| } |
| } |
| return true; |
| } |
| synchronized (mStateMachines) { |
| if (!connectionAllowedCheckMaxDevices(device)) { |
| // when mMaxConnectedAudioDevices is one, disconnect current device first. |
| if (mMaxConnectedAudioDevices == 1) { |
| List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( |
| new int[] {BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTING}); |
| for (BluetoothDevice sink : sinks) { |
| if (sink.equals(device)) { |
| Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); |
| continue; |
| } |
| disconnect(sink, contextType); |
| } |
| } else { |
| Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); |
| return false; |
| } |
| } |
| AcmStateMachine smConnect = getOrCreateStateMachine(device); |
| if (smConnect == null) { |
| Log.e(TAG, "Cannot connect to " + device + " : no state machine"); |
| return false; |
| } |
| Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); |
| msg.obj = preferredContext; |
| msg.arg1 = contextType; |
| msg.arg2 = profileType; |
| smConnect.sendMessage(msg); |
| return true; |
| } |
| } |
| |
| /** |
| * Disconnects Acm for the remote bluetooth device |
| * |
| * @param device is the device with which we would like to disconnect acm |
| * @return true if profile disconnected, false if device not connected over acm |
| */ |
| public boolean disconnect(BluetoothDevice device, int contextType) { |
| |
| if (DBG) { |
| Log.d(TAG, "disconnect(): " + device); |
| } |
| |
| if (device.getAddress().contains("9E:8B:00:00:00")) { |
| Log.d(TAG, "Disonnect request for group"); |
| byte[] addrByte = Utils.getByteAddress(device); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> d = mCsipManager.getSetMembers(set_id); |
| if (d == null) { |
| Log.d(TAG, "No set member found"); |
| return false; |
| } |
| Iterator<BluetoothDevice> members = d.iterator(); |
| if (members != null) { |
| while (members.hasNext()) { |
| BluetoothDevice addr = members.next(); |
| Log.d(TAG, "disconnect member: " + device); |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(addr); |
| if (sm == null) { |
| Log.e(TAG, "Ignored disconnect request for " + addr + " : no state machine"); |
| return false; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); |
| msg.obj = contextType; |
| sm.sendMessage(msg); |
| } |
| } |
| return true; |
| } |
| } |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine"); |
| return false; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); |
| msg.obj = contextType; |
| sm.sendMessage(msg); |
| return true; |
| } |
| } |
| |
| public List<BluetoothDevice> getConnectedDevices() { |
| |
| synchronized (mStateMachines) { |
| List<BluetoothDevice> devices = new ArrayList<>(); |
| for (AcmStateMachine sm : mStateMachines.values()) { |
| if (sm.isConnected()) { |
| devices.add(sm.getDevice()); |
| } |
| } |
| return devices; |
| } |
| } |
| |
| //check if it can be a list ? |
| public BluetoothDevice getCsipLockRequestedDevice() { |
| |
| synchronized (mStateMachines) { |
| BluetoothDevice device = null; |
| for (AcmStateMachine sm : mStateMachines.values()) { |
| if (sm.isCsipLockRequested()) { |
| device = sm.getDevice(); |
| } |
| } |
| return device; |
| } |
| } |
| |
| public boolean IsLockSupportAvailable(BluetoothDevice device) { |
| boolean isLockSupported = false; |
| /*int setId = mSetCoordinator.getRemoteSetId(device, ACM_UUID); |
| DeviceGroup set = mSetCoordinator.getDeviceGroup(setId); |
| isLockSupported = set.mLockSupport;*/ |
| //isLockSupported = mAdapterService.isCsipLockSupport(device); |
| Log.d(TAG, "Exclusive Access SupportAvaible for:" + device + "returns " + isLockSupported); |
| return isLockSupported; |
| } |
| |
| /** |
| * Check whether can connect to a peer device. |
| * The check considers the maximum number of connected peers. |
| * |
| * @param device the peer device to connect to |
| * @return true if connection is allowed, otherwise false |
| */ |
| private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { |
| int connected = 0; |
| // Count devices that are in the process of connecting or already connected |
| synchronized (mStateMachines) { |
| for (AcmStateMachine sm : mStateMachines.values()) { |
| switch (sm.getConnectionState()) { |
| case BluetoothProfile.STATE_CONNECTING: |
| case BluetoothProfile.STATE_CONNECTED: |
| if (Objects.equals(device, sm.getDevice())) { |
| return true; // Already connected or accounted for |
| } |
| connected++; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| return (connected < mMaxConnectedAudioDevices); |
| } |
| |
| List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| |
| List<BluetoothDevice> devices = new ArrayList<>(); |
| if (states == null) { |
| return devices; |
| } |
| final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); |
| if (bondedDevices == null) { |
| return devices; |
| } |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : bondedDevices) { |
| /*if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device), |
| BluetoothUuid.ACM_SINK)) { |
| continue; |
| }*/ |
| int connectionState = BluetoothProfile.STATE_DISCONNECTED; |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm != null) { |
| connectionState = sm.getConnectionState(); |
| } |
| for (int state : states) { |
| if (connectionState == state) { |
| devices.add(device); |
| break; |
| } |
| } |
| } |
| return devices; |
| } |
| } |
| |
| /** |
| * Get the list of devices that have state machines. |
| * |
| * @return the list of devices that have state machines |
| */ |
| @VisibleForTesting |
| List<BluetoothDevice> getDevices() { |
| List<BluetoothDevice> devices = new ArrayList<>(); |
| synchronized (mStateMachines) { |
| for (AcmStateMachine sm : mStateMachines.values()) { |
| devices.add(sm.getDevice()); |
| } |
| return devices; |
| } |
| } |
| |
| public int getConnectionState(BluetoothDevice device) { |
| |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| return sm.getConnectionState(); |
| } |
| } |
| |
| public int getCsipConnectionState(BluetoothDevice device) { |
| |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| return sm.getCsipConnectionState(); |
| } |
| } |
| |
| // Handle messages from native (JNI) to Java |
| void messageFromNative(AcmStackEvent stackEvent) { |
| Objects.requireNonNull(stackEvent.device, |
| "Device should never be null, event: " + stackEvent); |
| synchronized (mStateMachines) { |
| BluetoothDevice device = stackEvent.device; |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| if (stackEvent.type == AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { |
| switch (stackEvent.valueInt1) { |
| case AcmStackEvent.CONNECTION_STATE_CONNECTED: |
| case AcmStackEvent.CONNECTION_STATE_CONNECTING: |
| // Create a new state machine only when connecting to a device |
| if (!connectionAllowedCheckMaxDevices(device)) { |
| Log.e(TAG, "Cannot connect to " + device |
| + " : too many connected devices"); |
| return; |
| } |
| sm = getOrCreateStateMachine(device); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| if (sm == null) { |
| Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); |
| return; |
| } |
| sm.sendMessage(AcmStateMachine.STACK_EVENT, stackEvent); |
| } |
| } |
| |
| private AcmStateMachine getOrCreateStateMachine(BluetoothDevice device) { |
| if (device == null) { |
| Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); |
| return null; |
| } |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm != null) { |
| return sm; |
| } |
| // Limit the maximum number of state machines to avoid DoS attack |
| if (mStateMachines.size() >= MAX_ACM_STATE_MACHINES) { |
| Log.e(TAG, "Maximum number of ACM state machines reached: " |
| + MAX_ACM_STATE_MACHINES); |
| return null; |
| } |
| if (DBG) { |
| Log.d(TAG, "Creating a new state machine for " + device); |
| } |
| sm = AcmStateMachine.make(device, this, mAcmNativeInterface, |
| mStateMachinesThread.getLooper()); |
| mStateMachines.put(device, sm); |
| return sm; |
| } |
| } |
| |
| private class BondStateChangedReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { |
| return; |
| } |
| int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, |
| BluetoothDevice.ERROR); |
| BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); |
| bondStateChanged(device, state); |
| } |
| } |
| |
| /** |
| * Process a change in the bonding state for a device. |
| * |
| * @param device the device whose bonding state has changed |
| * @param bondState the new bond state for the device. Possible values are: |
| * {@link BluetoothDevice#BOND_NONE}, |
| * {@link BluetoothDevice#BOND_BONDING}, |
| * {@link BluetoothDevice#BOND_BONDED}. |
| */ |
| @VisibleForTesting |
| void bondStateChanged(BluetoothDevice device, int bondState) { |
| if (DBG) { |
| Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); |
| } |
| // Remove state machine if the bonding for a device is removed |
| if (bondState != BluetoothDevice.BOND_NONE) { |
| return; |
| } |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return; |
| } |
| if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { |
| return; |
| } |
| } |
| removeStateMachine(device); |
| } |
| |
| private void removeStateMachine(BluetoothDevice device) { |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| Log.w(TAG, "removeStateMachine: device " + device |
| + " does not have a state machine"); |
| return; |
| } |
| Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); |
| sm.doQuit(); |
| sm.cleanup(); |
| mStateMachines.remove(device); |
| mAcmDevices.remove(device); |
| } |
| } |
| |
| void updateLeaChannelMode(int state, BluetoothDevice device) { |
| BluetoothDevice peerLeaDevice = null; |
| peerLeaDevice = getLeaPeerDevice(device); |
| if (peerLeaDevice == null) { |
| Log.d(TAG, "updateLeaChannelMode: peer device is NULL"); |
| return; |
| } |
| Log.d(TAG, "LeaChannelMode: " + mLeaChannelMode + "state: " + state); |
| synchronized(mBtLeaLock) { |
| if ("mono".equals(mLeaChannelMode)) { |
| if ((state == BluetoothA2dp.STATE_PLAYING) && (peerLeaDevice!= null) |
| && peerLeaDevice.isConnected() && isAcmPlayingMusic(peerLeaDevice)) { |
| Log.d(TAG, "updateLeaChannelMode: send delay message to set stereo "); |
| Message msg = mHandler.obtainMessage(SET_EBSTEREO_CFG); |
| mHandler.sendMessageDelayed(msg, StereoCfg_Timeout); |
| } else if (state == BluetoothA2dp.STATE_PLAYING) { |
| Log.d(TAG, "updateLeaChannelMode: setparameters to Mono"); |
| synchronized (mAudioManagerLock) { |
| if (mAudioManager != null) { |
| Log.d(TAG, "updateLeaChannelMode: Acquired mVariableLock"); |
| mAudioManager.setParameters("LeaChannelConfig=mono"); |
| } |
| } |
| Log.d(TAG, "updateLeaChannelMode: Released mVariableLock"); |
| } |
| if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && |
| isAcmPlayingMusic(peerLeaDevice)) { |
| if (mHandler.hasMessages(StereoCfg_Timeout)) { |
| Log.d(TAG, "updateLeaChannelMode:remove delay message for stereo"); |
| mHandler.removeMessages(StereoCfg_Timeout); |
| } |
| } |
| } else if ("stereo".equals(mLeaChannelMode)) { |
| if ((state == BluetoothA2dp.STATE_PLAYING) && |
| (getConnectionState(peerLeaDevice) != BluetoothProfile.STATE_CONNECTED |
| || !isAcmPlayingMusic(peerLeaDevice))) { |
| Log.d(TAG, "updateLeaChannelMode: send delay message to set mono"); |
| Message msg = mHandler.obtainMessage(SET_EBMONO_CFG); |
| mHandler.sendMessageDelayed(msg, MonoCfg_Timeout); |
| } |
| if ((state == BluetoothA2dp.STATE_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { |
| if (mHandler.hasMessages(SET_EBMONO_CFG)) { |
| Log.d(TAG, "updateLeaChannelMode: remove delay message to set mono"); |
| mHandler.removeMessages(SET_EBMONO_CFG); |
| } |
| } |
| if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { |
| Log.d(TAG, "setparameters to Mono"); |
| synchronized (mAudioManagerLock) { |
| if (mAudioManager != null) |
| mAudioManager.setParameters("LeaChannelConfig=mono"); |
| } |
| mLeaChannelMode = "mono"; |
| } |
| } |
| } |
| } |
| |
| private BluetoothDevice getLeaPeerDevice(BluetoothDevice device) { |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return null; |
| } |
| return sm.getPeerDevice(); |
| } |
| } |
| |
| public boolean isPeerDeviceConnected(BluetoothDevice device, int setid) { |
| boolean isConnected = false; |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if ((d.msetID == setid) && !Objects.equals(dm, device)) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| isConnected = true; |
| Log.d(TAG, "At least one member is in connected state"); |
| break; |
| } |
| } |
| } |
| } |
| return isConnected; |
| } |
| |
| public boolean isPeerDeviceStreamingMusic(BluetoothDevice device, int setid) { |
| boolean isStreaming = false; |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if ((d.msetID == setid) && !Objects.equals(dm, device)) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| synchronized (mBtAcmLock) { |
| AcmStateMachine sm = mStateMachines.get(dm); |
| if (sm == null) { |
| return false; |
| } |
| if (sm.isMusicPlaying()) { |
| isStreaming = true; |
| Log.d(TAG, "At least one member is streaming for music"); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| return isStreaming; |
| } |
| |
| public boolean isShoPendingStop() { |
| Log.d(TAG, "isShoPendingStop " + mShoPend); |
| return mShoPend; |
| } |
| |
| public void resetShoPendingStop() { |
| mShoPend = false; |
| } |
| |
| public boolean isVoiceShoPendingStop() { |
| Log.d(TAG, "isVoiceShoPendingStop " + mVoiceShoPend); |
| return mVoiceShoPend; |
| } |
| |
| public void resetVoiceShoPendingStop() { |
| mVoiceShoPend = false; |
| } |
| |
| public BluetoothDevice getVoiceActiveDevice() { |
| return mActiveDeviceVoice; |
| } |
| |
| public void removePeersFromBgWl(BluetoothDevice device, int setid) { |
| synchronized (mStateMachines) { |
| BluetoothDevice d = null; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(setid); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| d = i.next(); |
| if (!(Objects.equals(d, device))) { |
| Log.d(TAG, "Device: " + d); |
| AcmStateMachine sm = mStateMachines.get(d); |
| if (sm == null) { |
| return; |
| } |
| sm.removeDevicefromBgWL(); |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean isAcmPlayingMusic(BluetoothDevice device) { |
| if (DBG) { |
| Log.d(TAG, "isAcmPlayingMusic(" + device + ")"); |
| } |
| synchronized (mBtAcmLock) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return false; |
| } |
| return sm.isMusicPlaying(); |
| } |
| } |
| |
| public boolean isAcmPlayingVoice(BluetoothDevice device) { |
| if (DBG) { |
| Log.d(TAG, "isAcmPlayingVoice(" + device + ")"); |
| } |
| synchronized (mBtAcmLock) { |
| AcmStateMachine sm = mStateMachines.get(device); |
| if (sm == null) { |
| return false; |
| } |
| return sm.isVoicePlaying(); |
| } |
| } |
| |
| public BluetoothDevice getGroup(BluetoothDevice device) { |
| Log.d(TAG, "Get group address for (" + device + ")"); |
| if (device.getAddress().contains("9E:8B:00:00:00")) { |
| Log.d(TAG, "Called for group address"); |
| return device; |
| } |
| BluetoothDevice dm = null; |
| |
| if (mAcmDevices != null) { |
| Log.d(TAG, "Hash Map is not NULL"); |
| BluetoothAcmDevice d = mAcmDevices.get(device); |
| if (d != null) |
| dm = d.mGrpDevice; |
| } |
| if (dm == null) { |
| Log.d(TAG, "Group address is NULL, make New"); |
| int Id = mCsipManager.getCsipSetId(device, null /*ACM_UUID*/); //TODO: UUID what to set ? |
| dm = makeGroupBdAddress(device, BluetoothProfile.STATE_DISCONNECTED, Id); |
| } |
| return dm; |
| } |
| |
| public int setActiveDevice(BluetoothDevice device, int contextType, int profileType, boolean playReq) { |
| |
| Log.d(TAG, "setActiveDevice: " + device + " contextType: " + contextType + " profileType: " + profileType + |
| " play req: " + playReq + " mActiveDeviceProfile: " + mActiveDeviceProfile+ " mActiveDeviceVoiceProfile: " + mActiveDeviceVoiceProfile); |
| |
| if (Objects.equals(device, mActiveDevice) && contextType == CONTEXT_TYPE_MUSIC && (mActiveDeviceProfile == profileType)) { |
| Log.e(TAG, "setActiveDevice(" + device + "): already set to active for media and profileType same as active profile"); |
| return ActiveDeviceManagerService.ALREADY_ACTIVE; |
| } |
| if (Objects.equals(device, mActiveDeviceVoice) && contextType == CONTEXT_TYPE_VOICE && (mActiveDeviceVoiceProfile == profileType)) { |
| Log.e(TAG, "setActiveDevice(" + device + "): already set to active for voice and profileType same as active profile"); |
| return ActiveDeviceManagerService.ALREADY_ACTIVE; |
| } |
| if (contextType == CONTEXT_TYPE_MUSIC) { |
| mShoPend = false; |
| if ((device == null) && (mActiveDevice != null)) { |
| if (mActiveDevice.getAddress().contains("9E:8B:00:00:00")) { |
| byte[] addrByte = Utils.getByteAddress(mActiveDevice); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(set_id); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| Log.d(TAG, "isAcmPlayingMusic(addr) " + isAcmPlayingMusic(addr)); |
| if (isAcmPlayingMusic(addr)) { |
| mShoPend = true; |
| break; |
| } |
| } |
| } |
| } else { |
| Log.d(TAG, "TWM active device"); |
| mShoPend = isAcmPlayingMusic(mActiveDevice); |
| } |
| } |
| Log.d(TAG, "mShoPend " + mShoPend); |
| } else if (contextType == CONTEXT_TYPE_VOICE) { |
| mVoiceShoPend = false; |
| if (mActiveDeviceVoice != null) { |
| if (mActiveDeviceVoice.getAddress().contains("9E:8B:00:00:00")) { |
| byte[] addrByte = Utils.getByteAddress(mActiveDeviceVoice); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(set_id); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| Log.d(TAG, "isAcmPlayingVoice(addr) " + isAcmPlayingVoice(addr)); |
| if (isAcmPlayingVoice(addr)) { |
| mVoiceShoPend = true; |
| break; |
| } |
| } |
| } |
| } else { |
| Log.d(TAG, "TWM active device"); |
| mVoiceShoPend = isAcmPlayingVoice(mActiveDeviceVoice); |
| } |
| } |
| Log.d(TAG, "mVoiceShoPend " + mVoiceShoPend); |
| } |
| Log.d(TAG, "old mActiveDevice: " + mActiveDevice + " & old mActiveDeviceVoice: " + mActiveDeviceVoice); |
| |
| if (setActiveDeviceAcm(device, contextType, profileType)) { |
| if (contextType == CONTEXT_TYPE_MUSIC) { |
| mActiveDevice = device; |
| mActiveDeviceProfile = profileType; |
| } else if (contextType == CONTEXT_TYPE_VOICE) { |
| mActiveDeviceVoice = device; |
| mActiveDeviceVoiceProfile = profileType; |
| } |
| Log.d(TAG, "new mActiveDevice: " + mActiveDevice + " & new mActiveDeviceVoice: " + mActiveDeviceVoice); |
| if(!playReq) { |
| if (mShoPend || mVoiceShoPend) { |
| Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); |
| return ActiveDeviceManagerService.SHO_PENDING; |
| } else { |
| Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_SUCCESS); |
| return ActiveDeviceManagerService.SHO_SUCCESS; |
| } |
| } else { |
| Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); |
| return ActiveDeviceManagerService.SHO_PENDING; |
| } |
| } |
| Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_FAILED); |
| mShoPend = false; |
| mVoiceShoPend = false; |
| return ActiveDeviceManagerService.SHO_FAILED; |
| } |
| |
| private boolean setActiveDeviceAcm(BluetoothDevice device, int contextType, int profileType) { |
| Log.d(TAG, "setActiveDeviceAcm: " + device); |
| try { |
| mAcmNativeInterfaceLock.readLock().lock(); |
| if (mAcmNativeInterface != null && !mAcmNativeInterface.setActiveDevice(device, profileType)) { |
| Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer"); |
| return false; |
| } |
| } finally { |
| mAcmNativeInterfaceLock.readLock().unlock(); |
| } |
| Log.d(TAG, "setActiveDeviceAcm(" + device + "): returns true"); |
| return true; |
| } |
| |
| public void setCodecConfigPreference(BluetoothDevice device, |
| BluetoothCodecConfig codecConfig, int contextType) { |
| |
| if (DBG) { |
| Log.d(TAG, "setCodecConfigPreference(" + device + "): " |
| + Objects.toString(codecConfig)); |
| } |
| mAcmCodecConfig.setCodecConfigPreference(device, codecConfig, contextType); |
| } |
| |
| public void ChangeCodecConfigPreference(BluetoothDevice device, |
| String mesg) { |
| |
| if (DBG) { |
| Log.d(TAG, "ChangeCodecConfigPreference " + device + "string: " + mesg); |
| } |
| if (device == null) |
| return; |
| mAcmName = mesg; |
| synchronized (mStateMachines) { |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if (Objects.equals(device, d.mGrpDevice)) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| AcmStateMachine sm = getOrCreateStateMachine(dm); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CODEC_CONFIG_CHANGED); |
| msg.obj = d.msetID; |
| sm.sendMessage(msg); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean StartStream(BluetoothDevice device, int contextType) { |
| if (DBG) { |
| Log.d(TAG, "startStream for " + device+ " context type: " + contextType); |
| } |
| if (device == null) |
| return false; |
| synchronized (mStateMachines) { |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if (Objects.equals(device, d.mGrpDevice)) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| AcmStateMachine sm = getOrCreateStateMachine(dm); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.START_STREAM); |
| msg.obj = contextType; |
| sm.sendMessage(msg); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| public boolean StopStream(BluetoothDevice device, int contextType) { |
| if (DBG) { |
| Log.d(TAG, "stopStream for " + device+ " context type: " + contextType); |
| } |
| if (device == null) |
| return false; |
| synchronized (mStateMachines) { |
| if (mAcmDevices.size() != 0) { |
| for (BluetoothDevice dm : mAcmDevices.keySet()) { |
| BluetoothAcmDevice d = mAcmDevices.get(dm); |
| if (Objects.equals(device, d.mGrpDevice)) { |
| if (d.mState == BluetoothProfile.STATE_CONNECTED) { |
| AcmStateMachine sm = getOrCreateStateMachine(dm); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.STOP_STREAM); |
| msg.obj = contextType; |
| sm.sendMessage(msg); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| public void setAbsoluteVolume(BluetoothDevice grpAddr, int volumeLevel, int audioType) { |
| //Convert volume level to VCP level before sending. |
| int vcpVolume = convertToVcpVolume(volumeLevel); |
| BluetoothDevice activeDevice = null; |
| Log.d(TAG, "AudioVolumeLevel " + volumeLevel + " vcpVolume " + vcpVolume); |
| |
| if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO || |
| audioType == ApmConst.AudioFeatures.CALL_AUDIO) { |
| ActiveDeviceManagerService activeDeviceManager = |
| ActiveDeviceManagerService.get(this); |
| activeDevice = activeDeviceManager.getActiveDevice(audioType); |
| Log.d(TAG, "activeDevice " + activeDevice + " grpAddr " + grpAddr |
| + " audioType " + audioType); |
| if (!grpAddr.equals(activeDevice)) { |
| Log.e(TAG, "Ignore setAbsoluteVolume for inactive device"); |
| return; |
| } |
| } else if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { |
| Log.d(TAG, "No active device, grpAddr " + grpAddr + " is broadcast device or group"); |
| } else { |
| Log.e(TAG, "Invalid audio type for set volume, ignore this request"); |
| return; |
| } |
| |
| if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { |
| mActiveDeviceLocalMediaVol = volumeLevel; |
| } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { |
| mActiveDeviceLocalVoiceVol = volumeLevel; |
| } |
| |
| if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { |
| Log.d(TAG, "setAbsoluteVolume for group addr, send abs vol to all members"); |
| byte[] addrByte = Utils.getByteAddress(grpAddr); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(set_id); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| Log.d(TAG, "send vol to member: " + addr); |
| mVcpController.setAbsoluteVolume(addr, vcpVolume, audioType); |
| } |
| } |
| } else { |
| Log.d(TAG, "setAbsoluteVolume for single addr, send abs vol to " + grpAddr); |
| mVcpController.setAbsoluteVolume(grpAddr, vcpVolume, audioType); |
| } |
| } |
| |
| public void setMute(BluetoothDevice grpAddr, boolean enableMute) { |
| if (mActiveDevice == null) { |
| Log.d(TAG, "No active device set, this grpAddr " + grpAddr + " is broadcast group"); |
| } else { |
| Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpAddr " + grpAddr); |
| } |
| |
| if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { |
| Log.d(TAG, "setMute for group addr, send mute/unmute to all members"); |
| byte[] addrByte = Utils.getByteAddress(grpAddr); |
| int set_id = addrByte[5]; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(set_id); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| Log.d(TAG, "send setMute to member: " + addr); |
| mVcpController.setMute(addr, enableMute); |
| } |
| } |
| } else { |
| Log.d(TAG, "setMute for single addr, send mute/unmute to " + grpAddr); |
| mVcpController.setMute(grpAddr, enableMute); |
| } |
| } |
| |
| public void onVolumeStateChanged(BluetoothDevice device, int vol, int audioType) { |
| VolumeManager service = VolumeManager.get(); |
| //Get or Make group address |
| BluetoothDevice grpDev = getGroup(device); |
| |
| if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { |
| Log.d(TAG, "volume notification for Broadcast audio"); |
| } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { |
| Log.d(TAG, "volume notification for Media audio"); |
| } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { |
| Log.d(TAG, "volume notification for Call audio"); |
| } else { |
| // remote volume change case |
| Log.d(TAG, "Volume change from remote: " + device + " vcp vol " + vol); |
| audioType = service.getActiveAudioType(device); |
| } |
| |
| //Convert vol |
| int audioVolume = convertToAudioStreamVolume(vol); |
| Log.d(TAG, "vcp vol " + vol + " audioVolume " + audioVolume); |
| |
| if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { |
| Log.d(TAG, "update volume to APM for Broadcast audio"); |
| service.onVolumeChange(grpDev, audioVolume, audioType); |
| } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { |
| //Check if new vol is diff than active group vol |
| Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalMediaVol: " + mActiveDeviceLocalMediaVol); |
| if (mActiveDeviceLocalMediaVol != audioVolume) { |
| Log.d(TAG, "new vol is different than mActiveDeviceLocalMediaVol update APM"); |
| service.onVolumeChange(grpDev, audioVolume, audioType); |
| mActiveDeviceLocalMediaVol = audioVolume; |
| } else { |
| Log.d(TAG, "local active media vol same as device new vol, ignore sending to APM"); |
| } |
| } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { |
| Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalVoiceVol: " + mActiveDeviceLocalVoiceVol); |
| if (mActiveDeviceLocalVoiceVol != audioVolume) { |
| Log.d(TAG, "new vol is different than mActiveDeviceLocalVoiceVol update APM"); |
| service.onVolumeChange(grpDev, audioVolume, audioType); |
| mActiveDeviceLocalVoiceVol = audioVolume; |
| } else { |
| Log.d(TAG, "local active call vol same as device new vol, ignore sending to APM"); |
| } |
| } else { |
| Log.d(TAG, "No audio is streaming and is inactive device, ignore sending to APM"); |
| } |
| |
| //Check if this device is group device, then take lock (ignored for now) and update vol to other members as well. |
| applyVolToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), vol, audioType); |
| } |
| |
| public void onMuteStatusChanged(BluetoothDevice device, boolean isMuted) { |
| VolumeManager service = VolumeManager.get(); |
| //Get or Make group address |
| BluetoothDevice grpDev = getGroup(device); |
| //default context type |
| int c_type = CONTEXT_TYPE_UNKNOWN; |
| |
| Log.d(TAG, "isMuted " + isMuted + " mActiveDeviceIsMuted " + mActiveDeviceIsMuted); |
| |
| if (!mVcpController.isBroadcastDevice(device) && ((mActiveDevice == null) || (mActiveDevice != grpDev))) { |
| Log.d(TAG, "unicast-mode, either Active device null or muteStatus changed for inactive device -- return"); |
| return; |
| } |
| |
| if ((mActiveDevice == null) || (mActiveDevice != grpDev)) { |
| Log.d(TAG, "No Active device or device is not active, seems in Broadcast mode"); |
| c_type = CONTEXT_TYPE_BROADCAST_AUDIO; |
| } else { |
| Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpDev " + grpDev + " device " + device); |
| c_type = CONTEXT_TYPE_MUSIC; |
| } |
| |
| //Check if new mute state is diff than active group mute state |
| if (mActiveDeviceIsMuted != isMuted) { |
| Log.d(TAG, "new mute state is different than mActiveDeviceIsMuted update APM"); |
| service.onMuteStatusChange(grpDev, isMuted, CONTEXT_TYPE_MUSIC); |
| mActiveDeviceIsMuted = isMuted; |
| } else { |
| Log.d(TAG, "local active device mute state same as device new mute state, ignore sending to APM, but send to other members"); |
| } |
| |
| //Check if this device is group device, then take lock(ignored for now) and update mute state to other members as well. |
| applyMuteStateToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), isMuted); |
| } |
| |
| public void setAbsVolSupport(BluetoothDevice device, boolean isAbsVolSupported, int initialVol) { |
| VolumeManager service = VolumeManager.get(); |
| //Get or Make group address |
| BluetoothDevice grpDev = getGroup(device); |
| if (grpDev == null) { |
| Log.d(TAG, "Group not created, return"); |
| return; |
| } |
| if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) && isAbsVolSupported) { |
| if (!isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { |
| Log.d(TAG, "VCP Peer device not connected, this is 1st member, update support to APM "); |
| //get current vol from VCP and send to APM during connection. |
| Log.d(TAG, "VCP initialVol " + initialVol + " when connected device " + device); |
| Log.d(TAG, "Update APM with connection vol " + convertToAudioStreamVolume(initialVol)); |
| service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, convertToAudioStreamVolume(initialVol), ApmConst.AudioProfiles.VCP); |
| |
| //get current mute state from VCP and sent to APM during connection. |
| Log.d(TAG, "VCP mute state " + mVcpController.isMute(device) + " when connected device " + device); |
| service.onMuteStatusChange(grpDev, mVcpController.isMute(device), CONTEXT_TYPE_MUSIC); |
| } else { |
| Log.d(TAG, "VCP Peer device connected, this is not 1st member, skip update to APM"); |
| } |
| } else if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_DISCONNECTED) && !isAbsVolSupported) { |
| if (isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { |
| Log.d(TAG, "VCP Peer device connected, this is not last member, skip update to APM "); |
| } else { |
| Log.d(TAG, "VCP Peer device not connected, this is last member, update to APM"); |
| service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, ApmConst.AudioProfiles.VCP); |
| } |
| } |
| } |
| |
| public boolean isVcpPeerDeviceConnected(BluetoothDevice device, int setid) { |
| boolean isVcpPeerConnected = false; |
| if (setid == INVALID_SET_ID) |
| return isVcpPeerConnected; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(setid); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return isVcpPeerConnected; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| if (!Objects.equals(addr, device) && (mVcpController.getConnectionState(addr) == BluetoothProfile.STATE_CONNECTED)) { |
| isVcpPeerConnected = true; |
| Log.d(TAG, "At least one other vcp member is in connected state"); |
| break; |
| } |
| } |
| } |
| return isVcpPeerConnected; |
| } |
| |
| private int convertToAudioStreamVolume(int volume) { |
| // Rescale volume to match AudioSystem's volume |
| return (int) Math.round((double) volume * mAudioStreamMax / VCP_MAX_VOL); |
| } |
| |
| private int convertToVcpVolume(int volume) { |
| return (int) Math.ceil((double) volume * VCP_MAX_VOL / mAudioStreamMax); |
| } |
| |
| private void applyVolToOtherMembers(BluetoothDevice device, int setid, int volume, int audioType) { |
| if (setid == INVALID_SET_ID) |
| return; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(setid); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| if (!Objects.equals(addr, device)) { |
| Log.d(TAG, "send vol changed to addr " + addr); |
| mVcpController.setAbsoluteVolume(addr, volume, audioType); |
| } |
| } |
| } |
| } |
| |
| private void applyMuteStateToOtherMembers(BluetoothDevice device, int setid, boolean muteState) { |
| if (setid == INVALID_SET_ID) |
| return; |
| List<BluetoothDevice> members = mCsipManager.getSetMembers(setid); |
| if (members == null) { |
| Log.d(TAG, "No set member found"); |
| return; |
| } |
| Iterator<BluetoothDevice> i = members.iterator(); |
| if (i != null) { |
| while (i.hasNext()) { |
| BluetoothDevice addr = i.next(); |
| if (!Objects.equals(addr, device)) { |
| Log.d(TAG, "send mute state to addr " + addr); |
| mVcpController.setMute(addr, muteState); |
| } |
| } |
| } |
| } |
| |
| public int getVcpConnState(BluetoothDevice device) { |
| return mVcpController.getConnectionState(device); |
| } |
| |
| public int getVcpConnMode(BluetoothDevice device) { |
| return mVcpController.getConnectionMode(device); |
| } |
| |
| public boolean isVcpMute(BluetoothDevice device) { |
| return mVcpController.isMute(device); |
| } |
| |
| public String getAcmName() { |
| return mAcmName; |
| } |
| |
| private static class AcmBinder extends Binder implements IProfileServiceBinder { |
| AcmBinder(AcmService service) { |
| } |
| @Override |
| public void cleanup() { |
| } |
| } |
| |
| private BluetoothGroupCallback mBluetoothGroupCallback = new BluetoothGroupCallback() { |
| public void onGroupClientAppRegistered(int status, int appId) { |
| Log.d(TAG, "onGroupClientAppRegistered, status: " + status + "appId: " + appId); |
| if (status == 0) { |
| mCsipManager.updateAppId(appId); |
| mIsCsipRegistered = true; |
| } else { |
| Log.e(TAG, "DeviceGroup registeration failed, status:" + status); |
| } |
| } |
| |
| public void onConnectionStateChanged (int state, BluetoothDevice device) { |
| Log.d(TAG, "onConnectionStateChanged: Device: " + device + "state: " + state); |
| //notify statemachine about device connection state |
| synchronized (mStateMachines) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_CONNECTION_STATE_CHANGED); |
| msg.obj = state; |
| sm.sendMessage(msg); |
| } |
| } |
| |
| public void onExclusiveAccessChanged (int setId, int value, int status, |
| List<BluetoothDevice> devices) { |
| Log.d(TAG, "onExclusiveAccessChanged: setId" + setId + devices + "status:" + status); |
| |
| //notify the statemachine about CSIP Lock status |
| switch (status) { |
| case ALL_LOCKS_ACQUIRED: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_LOCKED); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| break; |
| } |
| |
| case SOME_LOCKS_ACQUIRED_REASON_TIMEOUT: //check if need to handle separately |
| case SOME_LOCKS_ACQUIRED_REASON_DISC: { |
| BluetoothDevice mDevice = getCsipLockRequestedDevice(); |
| if (devices.contains(mDevice)) { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_PARTIAL_LOCK); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } else { |
| Log.d(TAG, "Exclusive access requested device is not present in list, release access for all devices"); |
| mCsipManager.setLock(setId, devices, mCsipManager.UNLOCK); |
| } |
| } break; |
| |
| case LOCK_DENIED: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_DENIED); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } break; |
| |
| case LOCK_RELEASED: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } break; |
| |
| case LOCK_RELEASED_TIMEOUT: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } break; |
| |
| /*case INVALID_REQUEST_PARAMS: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_INVALID_PARAM); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } break;*/ |
| |
| /*case LOCK_RELEASE_NOT_ALLOWED: { |
| synchronized (mStateMachines) { |
| for (BluetoothDevice device : devices) { |
| AcmStateMachine sm = getOrCreateStateMachine(device); |
| if (sm == null) { |
| //TODO:handle this case |
| continue; |
| } |
| Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASE); |
| msg.obj = setId; |
| msg.arg1 = value; |
| sm.sendMessage(msg); |
| } |
| } |
| } break;*/ |
| |
| case INVALID_VALUE: |
| break; |
| } |
| } |
| |
| //public void onSetMemberFound (int setId, BluetoothDevice device) { } |
| |
| //public void onSetDiscoveryStatusChanged (int setId, int status, int reason) { } |
| |
| //public void onLockAvailable (int setId, BluetoothDevice device) { } |
| |
| //public void onNewSetFound (int setId, BluetoothDevice device, UUID uuid) { } |
| |
| //public void onLockStatusFetched (int setId, int lockStatus) { } |
| }; |
| |
| public CsipManager getCsipManager(){ |
| return mCsipManager; |
| } |
| |
| class CsipManager { |
| public int LOCK = 0; |
| public int UNLOCK = 0; |
| |
| int mCsipAppid; |
| |
| CsipManager() { |
| LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; |
| UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; |
| |
| registerCsip(); |
| } |
| |
| CsipManager(BluetoothGroupCallback callbacks) { |
| LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; |
| UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; |
| |
| registerCsip(callbacks); |
| } |
| |
| void updateAppId(int id) { |
| mCsipAppid = id; |
| } |
| |
| int getAppId() { |
| return mCsipAppid; |
| } |
| |
| void registerCsip() { |
| GroupService mGroupService = GroupService.getGroupService(); |
| if(mGroupService != null) { |
| Log.i(TAG, "mGroupService is not null, register"); |
| mGroupService.registerGroupClientModule(mBluetoothGroupCallback); |
| } |
| } |
| |
| void registerCsip(BluetoothGroupCallback callbacks) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| if(mGroupService != null) { |
| Log.i(TAG, "mGroupService is not null, register " + callbacks); |
| mGroupService.registerGroupClientModule(callbacks); |
| } |
| } |
| |
| void unregisterCsip() { |
| GroupService mGroupService = GroupService.getGroupService(); |
| if (mGroupService != null) |
| mGroupService.unregisterGroupClientModule(mCsipAppid); |
| } |
| |
| void connectCsip(BluetoothDevice device) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| mGroupService.connect(mCsipAppid, device); |
| } |
| |
| void disconnectCsip(BluetoothDevice device) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| mGroupService.disconnect(mCsipAppid, device); |
| } |
| |
| void setLock(int setId, List<BluetoothDevice> devices, int lockVal) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| mGroupService.setLockValue(mCsipAppid, setId, devices, lockVal); |
| } |
| |
| DeviceGroup getCsipSet(int setId) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| return mGroupService.getCoordinatedSet(setId); |
| } |
| |
| List<BluetoothDevice> getSetMembers(int setId) { |
| DeviceGroup set = getCsipSet(setId); |
| if(set != null) |
| return set.getDeviceGroupMembers(); |
| return null; |
| } |
| |
| int getCsipSetId(BluetoothDevice device, ParcelUuid uuid) { |
| GroupService mGroupService = GroupService.getGroupService(); |
| return mGroupService.getRemoteDeviceGroupId(device, uuid); |
| //return -1; |
| } |
| } |
| } |