1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothProfile; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.os.SystemProperties; 23 import android.sysprop.BluetoothProperties; 24 import android.util.Log; 25 26 import com.android.bluetooth.R; 27 import com.android.bluetooth.Utils; 28 import com.android.bluetooth.a2dp.A2dpService; 29 import com.android.bluetooth.a2dpsink.A2dpSinkService; 30 import com.android.bluetooth.avrcp.AvrcpTargetService; 31 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; 32 import com.android.bluetooth.bas.BatteryService; 33 import com.android.bluetooth.bass_client.BassClientService; 34 import com.android.bluetooth.csip.CsipSetCoordinatorService; 35 import com.android.bluetooth.gatt.GattService; 36 import com.android.bluetooth.hap.HapClientService; 37 import com.android.bluetooth.hearingaid.HearingAidService; 38 import com.android.bluetooth.hfp.HeadsetService; 39 import com.android.bluetooth.hfpclient.HeadsetClientService; 40 import com.android.bluetooth.hid.HidDeviceService; 41 import com.android.bluetooth.hid.HidHostService; 42 import com.android.bluetooth.le_audio.LeAudioService; 43 import com.android.bluetooth.map.BluetoothMapService; 44 import com.android.bluetooth.mapclient.MapClientService; 45 import com.android.bluetooth.mcp.McpService; 46 import com.android.bluetooth.opp.BluetoothOppService; 47 import com.android.bluetooth.pan.PanService; 48 import com.android.bluetooth.pbap.BluetoothPbapService; 49 import com.android.bluetooth.pbapclient.PbapClientService; 50 import com.android.bluetooth.sap.SapService; 51 import com.android.bluetooth.tbs.TbsService; 52 import com.android.bluetooth.vc.VolumeControlService; 53 import com.android.internal.annotations.VisibleForTesting; 54 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.HashSet; 58 import java.util.Iterator; 59 import java.util.Set; 60 61 public class Config { 62 private static final String TAG = "AdapterServiceConfig"; 63 64 private static final String FEATURE_HEARING_AID = "settings_bluetooth_hearing_aid"; 65 private static final String FEATURE_BATTERY = "settings_bluetooth_battery"; 66 67 private static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; 68 private static final String PERSIST_PREFIX = "persist." + FFLAG_OVERRIDE_PREFIX; 69 70 private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY = 71 "ro.bluetooth.leaudio_switcher.supported"; 72 private static final String LE_AUDIO_BROADCAST_DYNAMIC_SWITCH_PROPERTY = 73 "ro.bluetooth.leaudio_broadcast_switcher.supported"; 74 private static final String LE_AUDIO_SWITCHER_DISABLED_PROPERTY = 75 "persist.bluetooth.leaudio_switcher.disabled"; 76 77 private static final Set<String> PERSISTENT_FLAGS = Set.of( 78 FEATURE_HEARING_AID, 79 FEATURE_BATTERY 80 ); 81 82 private static class ProfileConfig { 83 Class mClass; 84 boolean mSupported; 85 long mMask; 86 ProfileConfig(Class theClass, boolean supported, long mask)87 ProfileConfig(Class theClass, boolean supported, long mask) { 88 mClass = theClass; 89 mSupported = supported; 90 mMask = mask; 91 } 92 } 93 94 /** 95 * List of profile services related to LE audio 96 */ 97 private static final HashSet<Class> mLeAudioUnicastProfiles = new HashSet<Class>( 98 Arrays.asList(LeAudioService.class, 99 VolumeControlService.class, 100 McpService.class, 101 CsipSetCoordinatorService.class)); 102 103 /** 104 * List of profile services with the profile-supported resource flag and bit mask. 105 */ 106 private static final ProfileConfig[] PROFILE_SERVICES_AND_FLAGS = { 107 new ProfileConfig(A2dpService.class, A2dpService.isEnabled(), 108 (1 << BluetoothProfile.A2DP)), 109 new ProfileConfig(A2dpSinkService.class, A2dpSinkService.isEnabled(), 110 (1 << BluetoothProfile.A2DP_SINK)), 111 new ProfileConfig(AvrcpTargetService.class, AvrcpTargetService.isEnabled(), 112 (1 << BluetoothProfile.AVRCP)), 113 new ProfileConfig(AvrcpControllerService.class, AvrcpControllerService.isEnabled(), 114 (1 << BluetoothProfile.AVRCP_CONTROLLER)), 115 new ProfileConfig(BassClientService.class, BassClientService.isEnabled(), 116 (1 << BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)), 117 new ProfileConfig(BatteryService.class, BatteryService.isEnabled(), 118 (1 << BluetoothProfile.BATTERY)), 119 new ProfileConfig(CsipSetCoordinatorService.class, 120 CsipSetCoordinatorService.isEnabled(), 121 (1 << BluetoothProfile.CSIP_SET_COORDINATOR)), 122 new ProfileConfig(HapClientService.class, HapClientService.isEnabled(), 123 (1 << BluetoothProfile.HAP_CLIENT)), 124 new ProfileConfig(HeadsetService.class, HeadsetService.isEnabled(), 125 (1 << BluetoothProfile.HEADSET)), 126 new ProfileConfig(HeadsetClientService.class, HeadsetClientService.isEnabled(), 127 (1 << BluetoothProfile.HEADSET_CLIENT)), 128 new ProfileConfig(HearingAidService.class, HearingAidService.isEnabled(), 129 (1 << BluetoothProfile.HEARING_AID)), 130 new ProfileConfig(HidDeviceService.class, HidDeviceService.isEnabled(), 131 (1 << BluetoothProfile.HID_DEVICE)), 132 new ProfileConfig(HidHostService.class, HidHostService.isEnabled(), 133 (1 << BluetoothProfile.HID_HOST)), 134 new ProfileConfig(GattService.class, GattService.isEnabled(), 135 (1 << BluetoothProfile.GATT)), 136 new ProfileConfig(LeAudioService.class, LeAudioService.isEnabled(), 137 (1 << BluetoothProfile.LE_AUDIO)), 138 new ProfileConfig(TbsService.class, TbsService.isEnabled(), 139 (1 << BluetoothProfile.LE_CALL_CONTROL)), 140 new ProfileConfig(BluetoothMapService.class, BluetoothMapService.isEnabled(), 141 (1 << BluetoothProfile.MAP)), 142 new ProfileConfig(MapClientService.class, MapClientService.isEnabled(), 143 (1 << BluetoothProfile.MAP_CLIENT)), 144 new ProfileConfig(McpService.class, McpService.isEnabled(), 145 (1 << BluetoothProfile.MCP_SERVER)), 146 new ProfileConfig(BluetoothOppService.class, BluetoothOppService.isEnabled(), 147 (1 << BluetoothProfile.OPP)), 148 new ProfileConfig(PanService.class, PanService.isEnabled(), 149 (1 << BluetoothProfile.PAN)), 150 new ProfileConfig(BluetoothPbapService.class, BluetoothPbapService.isEnabled(), 151 (1 << BluetoothProfile.PBAP)), 152 new ProfileConfig(PbapClientService.class, PbapClientService.isEnabled(), 153 (1 << BluetoothProfile.PBAP_CLIENT)), 154 new ProfileConfig(SapService.class, SapService.isEnabled(), 155 (1 << BluetoothProfile.SAP)), 156 new ProfileConfig(VolumeControlService.class, VolumeControlService.isEnabled(), 157 (1 << BluetoothProfile.VOLUME_CONTROL)), 158 }; 159 160 /** 161 * A test function to allow for dynamic enabled 162 */ 163 @VisibleForTesting setProfileEnabled(Class profileClass, boolean enabled)164 public static void setProfileEnabled(Class profileClass, boolean enabled) { 165 if (profileClass == null) { 166 return; 167 } 168 for (ProfileConfig profile : PROFILE_SERVICES_AND_FLAGS) { 169 if (profileClass.equals(profile.mClass)) { 170 profile.mSupported = enabled; 171 } 172 } 173 } 174 175 private static Class[] sSupportedProfiles = new Class[0]; 176 177 private static boolean sIsGdEnabledUptoScanningLayer = false; 178 init(Context ctx)179 static void init(Context ctx) { 180 if (LeAudioService.isBroadcastEnabled()) { 181 updateSupportedProfileMask( 182 true, LeAudioService.class, BluetoothProfile.LE_AUDIO_BROADCAST); 183 } 184 185 final boolean leAudioDynamicSwitchSupported = 186 SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false); 187 188 if (leAudioDynamicSwitchSupported) { 189 final String leAudioSwitcherDisabled = SystemProperties 190 .get(LE_AUDIO_SWITCHER_DISABLED_PROPERTY, "none"); 191 if (leAudioSwitcherDisabled.equals("true")) { 192 setLeAudioProfileStatus(false); 193 } else if (leAudioSwitcherDisabled.equals("false")) { 194 setLeAudioProfileStatus(true); 195 } 196 } 197 198 // Disable ASHA on Automotive, TV, and Watch devices if the system property is not set 199 // This means that the OS will not automatically enable ASHA on these platforms, but these 200 // platforms can choose to enable ASHA themselves 201 if (BluetoothProperties.isProfileAshaCentralEnabled().isEmpty()) { 202 if (Utils.isAutomotive(ctx) || Utils.isTv(ctx) || Utils.isWatch(ctx)) { 203 setProfileEnabled(HearingAidService.class, false); 204 } 205 } 206 207 // Disable ASHA if BLE is not supported on this platform even if the platform enabled ASHA 208 // accidentally 209 if (!Utils.isBleSupported(ctx)) { 210 setProfileEnabled(HearingAidService.class, false); 211 } 212 213 ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length); 214 for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) { 215 Log.i(TAG, "init: profile=" + config.mClass.getSimpleName() + ", enabled=" 216 + config.mSupported); 217 if (config.mSupported) { 218 profiles.add(config.mClass); 219 } 220 } 221 sSupportedProfiles = profiles.toArray(new Class[profiles.size()]); 222 223 if (ctx == null) { 224 return; 225 } 226 Resources resources = ctx.getResources(); 227 if (resources == null) { 228 return; 229 } 230 sIsGdEnabledUptoScanningLayer = resources.getBoolean(R.bool.enable_gd_up_to_scanning_layer); 231 } 232 setLeAudioProfileStatus(Boolean enable)233 static void setLeAudioProfileStatus(Boolean enable) { 234 setProfileEnabled(CsipSetCoordinatorService.class, enable); 235 setProfileEnabled(HapClientService.class, enable); 236 setProfileEnabled(LeAudioService.class, enable); 237 setProfileEnabled(TbsService.class, enable); 238 setProfileEnabled(McpService.class, enable); 239 setProfileEnabled(VolumeControlService.class, enable); 240 241 final boolean broadcastDynamicSwitchSupported = 242 SystemProperties.getBoolean(LE_AUDIO_BROADCAST_DYNAMIC_SWITCH_PROPERTY, false); 243 244 if (broadcastDynamicSwitchSupported) { 245 setProfileEnabled(BassClientService.class, enable); 246 updateSupportedProfileMask( 247 enable, LeAudioService.class, BluetoothProfile.LE_AUDIO_BROADCAST); 248 } 249 } 250 251 /** 252 * Remove the input profiles from the supported list. 253 */ removeProfileFromSupportedList(HashSet<Class> nonSupportedProfiles)254 static void removeProfileFromSupportedList(HashSet<Class> nonSupportedProfiles) { 255 ArrayList<Class> profilesList = new ArrayList<Class>(Arrays.asList(sSupportedProfiles)); 256 Iterator<Class> iter = profilesList.iterator(); 257 258 while (iter.hasNext()) { 259 Class profileClass = iter.next(); 260 261 if (nonSupportedProfiles.contains(profileClass)) { 262 iter.remove(); 263 Log.v(TAG, "Remove " + profileClass.getSimpleName() + " from supported list."); 264 } 265 } 266 267 sSupportedProfiles = profilesList.toArray(new Class[profilesList.size()]); 268 } 269 updateSupportedProfileMask(Boolean enable, Class profile, int supportedProfile)270 static void updateSupportedProfileMask(Boolean enable, Class profile, int supportedProfile) { 271 for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) { 272 if (config.mClass == profile) { 273 if (enable) { 274 config.mMask |= 1 << supportedProfile; 275 } else { 276 config.mMask &= ~(1 << supportedProfile); 277 } 278 return; 279 } 280 } 281 } 282 getLeAudioUnicastProfiles()283 static HashSet<Class> getLeAudioUnicastProfiles() { 284 return mLeAudioUnicastProfiles; 285 } 286 getSupportedProfiles()287 static Class[] getSupportedProfiles() { 288 return sSupportedProfiles; 289 } 290 isGdEnabledUpToScanningLayer()291 static boolean isGdEnabledUpToScanningLayer() { 292 return sIsGdEnabledUptoScanningLayer; 293 } 294 getProfileMask(Class profile)295 private static long getProfileMask(Class profile) { 296 for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) { 297 if (config.mClass == profile) { 298 return config.mMask; 299 } 300 } 301 Log.w(TAG, "Could not find profile bit mask for " + profile.getSimpleName()); 302 return 0; 303 } 304 getSupportedProfilesBitMask()305 static long getSupportedProfilesBitMask() { 306 long mask = 0; 307 for (final Class profileClass : getSupportedProfiles()) { 308 mask |= getProfileMask(profileClass); 309 } 310 return mask; 311 } 312 } 313