Loading android/app/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -405,6 +405,14 @@ <action android:name="android.bluetooth.IBluetoothHearingAid"/> </intent-filter> </service> <service android:process="@string/process" android:name = ".le_audio.LeAudioService" android:exported = "true"> <intent-filter> <action android:name="android.bluetooth.IBluetoothLeAudio" /> </intent-filter> </service> <!-- Authenticator for PBAP account. --> <service android:process="@string/process" android:name=".pbapclient.AuthenticationService" Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java 0 → 100644 +393 −0 Original line number Diff line number Diff line /* * Copyright 2020 HIMSA II K/S - www.himsa.com. * Represented by EHIMA - www.ehima.com * * 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.le_audio; import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothLeAudio; import android.os.HandlerThread; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Provides Bluetooth LeAudio profile, as a service in the Bluetooth application. * @hide */ public class LeAudioService extends ProfileService { private static final boolean DBG = true; private static final String TAG = "LeAudioService"; private static LeAudioService sLeAudioService; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; @Override protected IProfileServiceBinder initBinder() { return new BluetoothLeAudioBinder(this); } @Override protected void create() { Log.i(TAG, "create()"); } @Override protected boolean start() { Log.i(TAG, "start()"); if (sLeAudioService != null) { throw new IllegalStateException("start() called twice"); } // Get AdapterService, AudioManager. None of them can be null. mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), "AdapterService cannot be null when LeAudioService starts"); mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), "DatabaseManager cannot be null when A2dpService starts"); // Mark service as started setLeAudioService(this); setActiveDevice(null); return true; } @Override protected boolean stop() { Log.i(TAG, "stop()"); if (sLeAudioService == null) { Log.w(TAG, "stop() called before start()"); return true; } setActiveDevice(null); // Set the service and BLE devices as inactive setLeAudioService(null); mAdapterService = null; return true; } @Override protected void cleanup() { Log.i(TAG, "cleanup()"); } public static synchronized LeAudioService getLeAudioService() { if (sLeAudioService == null) { Log.w(TAG, "getLeAudioService(): service is NULL"); return null; } if (!sLeAudioService.isAvailable()) { Log.w(TAG, "getLeAudioService(): service is not available"); return null; } return sLeAudioService; } private static synchronized void setLeAudioService(LeAudioService instance) { if (DBG) { Log.d(TAG, "setLeAudioService(): set to: " + instance); } sLeAudioService = instance; } public boolean connect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "connect(): " + device); } if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); return false; } //TODO: implement return false; } /** * Disconnects LE Audio for the remote bluetooth device * * @param device is the device with which we would like to disconnect LE Audio * @return true if profile disconnected, false if device not connected over LE Audio */ public boolean disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "disconnect(): " + device); } //TODO: implement return false; } List<BluetoothDevice> getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<>(); return devices; } List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); ArrayList<BluetoothDevice> devices = new ArrayList<>(); return devices; } public int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); //TODO: implement return BluetoothProfile.STATE_DISCONNECTED; } /** * Set the active device. * * @param device the new active device * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "setActiveDevice:" + device); } return true; } /** * Get the connected physical LeAudio devices that are active. * * @return the list of active devices. */ List<BluetoothDevice> getActiveDevices() { if (DBG) { Log.d(TAG, "getActiveDevices"); } ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); return activeDevices; } /** * Set connection policy of the profile and connects it if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} * * <p> The device should already be paired. * Connection policy can be one of: * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} * * @param device the remote device * @param connectionPolicy is the connection policy to set to for this profile * @return true on success, otherwise false */ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); if (DBG) { Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); } if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, connectionPolicy)) { return false; } if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { connect(device); } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { disconnect(device); } return true; } /** * Get the connection policy of the profile. * * <p> The connection policy can be any of: * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} * * @param device Bluetooth device * @return connection policy of the device * @hide */ public int getConnectionPolicy(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return mDatabaseManager .getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO); } /** * Get device group id. Devices with same group id belong to same group (i.e left and right * earbud) * @param device LE Audio capable device * @return group id that this device currently belongs to */ public int getGroupId(BluetoothDevice device) { if (device == null) { return LE_AUDIO_GROUP_ID_INVALID; } //TODO: implement return LE_AUDIO_GROUP_ID_INVALID; } /** * Binder object: must be a static class or memory leak may occur */ @VisibleForTesting static class BluetoothLeAudioBinder extends IBluetoothLeAudio.Stub implements IProfileServiceBinder { private LeAudioService mService; private LeAudioService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "LeAudio call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } BluetoothLeAudioBinder(LeAudioService svc) { mService = svc; } @Override public void cleanup() { mService = null; } @Override public boolean connect(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.connect(device); } @Override public boolean disconnect(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.disconnect(device); } @Override public List<BluetoothDevice> getConnectedDevices() { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(0); } return service.getConnectedDevices(); } @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(0); } return service.getDevicesMatchingConnectionStates(states); } @Override public int getConnectionState(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return BluetoothProfile.STATE_DISCONNECTED; } return service.getConnectionState(device); } @Override public boolean setActiveDevice(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.setActiveDevice(device); } @Override public List<BluetoothDevice> getActiveDevices() { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(); } return service.getActiveDevices(); } @Override public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { LeAudioService service = getService(); if (service == null) { return false; } return service.setConnectionPolicy(device, connectionPolicy); } @Override public int getConnectionPolicy(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; } return service.getConnectionPolicy(device); } @Override public int getGroupId(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return LE_AUDIO_GROUP_ID_INVALID; } return service.getGroupId(device); } } @Override public void dump(StringBuilder sb) { super.dump(sb); // TODO: Dump all state machines } } Loading
android/app/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -405,6 +405,14 @@ <action android:name="android.bluetooth.IBluetoothHearingAid"/> </intent-filter> </service> <service android:process="@string/process" android:name = ".le_audio.LeAudioService" android:exported = "true"> <intent-filter> <action android:name="android.bluetooth.IBluetoothLeAudio" /> </intent-filter> </service> <!-- Authenticator for PBAP account. --> <service android:process="@string/process" android:name=".pbapclient.AuthenticationService" Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java 0 → 100644 +393 −0 Original line number Diff line number Diff line /* * Copyright 2020 HIMSA II K/S - www.himsa.com. * Represented by EHIMA - www.ehima.com * * 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.le_audio; import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothLeAudio; import android.os.HandlerThread; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Provides Bluetooth LeAudio profile, as a service in the Bluetooth application. * @hide */ public class LeAudioService extends ProfileService { private static final boolean DBG = true; private static final String TAG = "LeAudioService"; private static LeAudioService sLeAudioService; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; @Override protected IProfileServiceBinder initBinder() { return new BluetoothLeAudioBinder(this); } @Override protected void create() { Log.i(TAG, "create()"); } @Override protected boolean start() { Log.i(TAG, "start()"); if (sLeAudioService != null) { throw new IllegalStateException("start() called twice"); } // Get AdapterService, AudioManager. None of them can be null. mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), "AdapterService cannot be null when LeAudioService starts"); mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), "DatabaseManager cannot be null when A2dpService starts"); // Mark service as started setLeAudioService(this); setActiveDevice(null); return true; } @Override protected boolean stop() { Log.i(TAG, "stop()"); if (sLeAudioService == null) { Log.w(TAG, "stop() called before start()"); return true; } setActiveDevice(null); // Set the service and BLE devices as inactive setLeAudioService(null); mAdapterService = null; return true; } @Override protected void cleanup() { Log.i(TAG, "cleanup()"); } public static synchronized LeAudioService getLeAudioService() { if (sLeAudioService == null) { Log.w(TAG, "getLeAudioService(): service is NULL"); return null; } if (!sLeAudioService.isAvailable()) { Log.w(TAG, "getLeAudioService(): service is not available"); return null; } return sLeAudioService; } private static synchronized void setLeAudioService(LeAudioService instance) { if (DBG) { Log.d(TAG, "setLeAudioService(): set to: " + instance); } sLeAudioService = instance; } public boolean connect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "connect(): " + device); } if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); return false; } //TODO: implement return false; } /** * Disconnects LE Audio for the remote bluetooth device * * @param device is the device with which we would like to disconnect LE Audio * @return true if profile disconnected, false if device not connected over LE Audio */ public boolean disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "disconnect(): " + device); } //TODO: implement return false; } List<BluetoothDevice> getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<>(); return devices; } List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); ArrayList<BluetoothDevice> devices = new ArrayList<>(); return devices; } public int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); //TODO: implement return BluetoothProfile.STATE_DISCONNECTED; } /** * Set the active device. * * @param device the new active device * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG, "setActiveDevice:" + device); } return true; } /** * Get the connected physical LeAudio devices that are active. * * @return the list of active devices. */ List<BluetoothDevice> getActiveDevices() { if (DBG) { Log.d(TAG, "getActiveDevices"); } ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); return activeDevices; } /** * Set connection policy of the profile and connects it if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} * * <p> The device should already be paired. * Connection policy can be one of: * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} * * @param device the remote device * @param connectionPolicy is the connection policy to set to for this profile * @return true on success, otherwise false */ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); if (DBG) { Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); } if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, connectionPolicy)) { return false; } if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { connect(device); } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { disconnect(device); } return true; } /** * Get the connection policy of the profile. * * <p> The connection policy can be any of: * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} * * @param device Bluetooth device * @return connection policy of the device * @hide */ public int getConnectionPolicy(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return mDatabaseManager .getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO); } /** * Get device group id. Devices with same group id belong to same group (i.e left and right * earbud) * @param device LE Audio capable device * @return group id that this device currently belongs to */ public int getGroupId(BluetoothDevice device) { if (device == null) { return LE_AUDIO_GROUP_ID_INVALID; } //TODO: implement return LE_AUDIO_GROUP_ID_INVALID; } /** * Binder object: must be a static class or memory leak may occur */ @VisibleForTesting static class BluetoothLeAudioBinder extends IBluetoothLeAudio.Stub implements IProfileServiceBinder { private LeAudioService mService; private LeAudioService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "LeAudio call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } BluetoothLeAudioBinder(LeAudioService svc) { mService = svc; } @Override public void cleanup() { mService = null; } @Override public boolean connect(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.connect(device); } @Override public boolean disconnect(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.disconnect(device); } @Override public List<BluetoothDevice> getConnectedDevices() { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(0); } return service.getConnectedDevices(); } @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(0); } return service.getDevicesMatchingConnectionStates(states); } @Override public int getConnectionState(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return BluetoothProfile.STATE_DISCONNECTED; } return service.getConnectionState(device); } @Override public boolean setActiveDevice(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return false; } return service.setActiveDevice(device); } @Override public List<BluetoothDevice> getActiveDevices() { LeAudioService service = getService(); if (service == null) { return new ArrayList<>(); } return service.getActiveDevices(); } @Override public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { LeAudioService service = getService(); if (service == null) { return false; } return service.setConnectionPolicy(device, connectionPolicy); } @Override public int getConnectionPolicy(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; } return service.getConnectionPolicy(device); } @Override public int getGroupId(BluetoothDevice device) { LeAudioService service = getService(); if (service == null) { return LE_AUDIO_GROUP_ID_INVALID; } return service.getGroupId(device); } } @Override public void dump(StringBuilder sb) { super.dump(sb); // TODO: Dump all state machines } }