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