1 /* 2 * Copyright (C) 2021 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.settingslib.bluetooth; 18 19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothClass; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothVolumeControl; 27 import android.content.Context; 28 import android.os.Build; 29 import android.util.Log; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 34 import androidx.annotation.RequiresApi; 35 36 /** 37 * VolumeControlProfile handles Bluetooth Volume Control Controller role 38 */ 39 public class VolumeControlProfile implements LocalBluetoothProfile { 40 private static final String TAG = "VolumeControlProfile"; 41 private static boolean DEBUG = true; 42 static final String NAME = "VCP"; 43 // Order of this profile in device profiles list 44 private static final int ORDINAL = 1; 45 46 private Context mContext; 47 private final CachedBluetoothDeviceManager mDeviceManager; 48 private final LocalBluetoothProfileManager mProfileManager; 49 50 private BluetoothVolumeControl mService; 51 private boolean mIsProfileReady; 52 53 // These callbacks run on the main thread. 54 private final class VolumeControlProfileServiceListener 55 implements BluetoothProfile.ServiceListener { 56 57 @RequiresApi(Build.VERSION_CODES.S) onServiceConnected(int profile, BluetoothProfile proxy)58 public void onServiceConnected(int profile, BluetoothProfile proxy) { 59 if (DEBUG) { 60 Log.d(TAG, "Bluetooth service connected"); 61 } 62 mService = (BluetoothVolumeControl) proxy; 63 // We just bound to the service, so refresh the UI for any connected 64 // VolumeControlProfile devices. 65 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 66 while (!deviceList.isEmpty()) { 67 BluetoothDevice nextDevice = deviceList.remove(0); 68 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 69 // we may add a new device here, but generally this should not happen 70 if (device == null) { 71 if (DEBUG) { 72 Log.d(TAG, "VolumeControlProfile found new device: " + nextDevice); 73 } 74 device = mDeviceManager.addDevice(nextDevice); 75 } 76 device.onProfileStateChanged(VolumeControlProfile.this, 77 BluetoothProfile.STATE_CONNECTED); 78 device.refresh(); 79 } 80 81 mProfileManager.callServiceConnectedListeners(); 82 mIsProfileReady = true; 83 } 84 onServiceDisconnected(int profile)85 public void onServiceDisconnected(int profile) { 86 if (DEBUG) { 87 Log.d(TAG, "Bluetooth service disconnected"); 88 } 89 mProfileManager.callServiceDisconnectedListeners(); 90 mIsProfileReady = false; 91 } 92 } 93 VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)94 VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager, 95 LocalBluetoothProfileManager profileManager) { 96 mContext = context; 97 mDeviceManager = deviceManager; 98 mProfileManager = profileManager; 99 100 BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, 101 new VolumeControlProfile.VolumeControlProfileServiceListener(), 102 BluetoothProfile.VOLUME_CONTROL); 103 } 104 105 @Override accessProfileEnabled()106 public boolean accessProfileEnabled() { 107 return false; 108 } 109 110 @Override isAutoConnectable()111 public boolean isAutoConnectable() { 112 return true; 113 } 114 115 /** 116 * Get VolumeControlProfile devices matching connection states{ 117 * 118 * @return Matching device list 119 * @code BluetoothProfile.STATE_CONNECTED, 120 * @code BluetoothProfile.STATE_CONNECTING, 121 * @code BluetoothProfile.STATE_DISCONNECTING} 122 */ getConnectedDevices()123 public List<BluetoothDevice> getConnectedDevices() { 124 if (mService == null) { 125 return new ArrayList<BluetoothDevice>(0); 126 } 127 return mService.getDevicesMatchingConnectionStates( 128 new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING, 129 BluetoothProfile.STATE_DISCONNECTING}); 130 } 131 132 @Override getConnectionStatus(BluetoothDevice device)133 public int getConnectionStatus(BluetoothDevice device) { 134 if (mService == null) { 135 return BluetoothProfile.STATE_DISCONNECTED; 136 } 137 return mService.getConnectionState(device); 138 } 139 140 @Override isEnabled(BluetoothDevice device)141 public boolean isEnabled(BluetoothDevice device) { 142 if (mService == null || device == null) { 143 return false; 144 } 145 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 146 } 147 148 @Override getConnectionPolicy(BluetoothDevice device)149 public int getConnectionPolicy(BluetoothDevice device) { 150 if (mService == null || device == null) { 151 return CONNECTION_POLICY_FORBIDDEN; 152 } 153 return mService.getConnectionPolicy(device); 154 } 155 156 @Override setEnabled(BluetoothDevice device, boolean enabled)157 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 158 boolean isSuccessful = false; 159 if (mService == null || device == null) { 160 return false; 161 } 162 if (DEBUG) { 163 Log.d(TAG, device.getAnonymizedAddress() + " setEnabled: " + enabled); 164 } 165 if (enabled) { 166 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 167 isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 168 } 169 } else { 170 isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 171 } 172 173 return isSuccessful; 174 } 175 176 @Override isProfileReady()177 public boolean isProfileReady() { 178 return mIsProfileReady; 179 } 180 181 @Override getProfileId()182 public int getProfileId() { 183 return BluetoothProfile.VOLUME_CONTROL; 184 } 185 toString()186 public String toString() { 187 return NAME; 188 } 189 190 @Override getOrdinal()191 public int getOrdinal() { 192 return ORDINAL; 193 } 194 195 @Override getNameResource(BluetoothDevice device)196 public int getNameResource(BluetoothDevice device) { 197 return 0; // VCP profile not displayed in UI 198 } 199 200 @Override getSummaryResourceForDevice(BluetoothDevice device)201 public int getSummaryResourceForDevice(BluetoothDevice device) { 202 return 0; // VCP profile not displayed in UI 203 } 204 205 @Override getDrawableResource(BluetoothClass btClass)206 public int getDrawableResource(BluetoothClass btClass) { 207 // no icon for VCP 208 return 0; 209 } 210 } 211