1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.settingslib.bluetooth;
19 
20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothClass;
25 import android.bluetooth.BluetoothCsipSetCoordinator;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothProfile;
28 import android.content.Context;
29 import android.os.ParcelUuid;
30 import android.util.Log;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import com.android.settingslib.R;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
39 
40 /**
41  * CSIP Set Coordinator handles Bluetooth CSIP Set Coordinator role profile.
42  */
43 public class CsipSetCoordinatorProfile implements LocalBluetoothProfile {
44     private static final String TAG = "CsipSetCoordinatorProfile";
45     private static final boolean VDBG = true;
46 
47     private Context mContext;
48 
49     private BluetoothCsipSetCoordinator mService;
50     private boolean mIsProfileReady;
51 
52     private final CachedBluetoothDeviceManager mDeviceManager;
53 
54     static final String NAME = "CSIP Set Coordinator";
55     private final LocalBluetoothProfileManager mProfileManager;
56 
57     // Order of this profile in device profiles list
58     private static final int ORDINAL = 1;
59 
60     // These callbacks run on the main thread.
61     private final class CoordinatedSetServiceListener implements BluetoothProfile.ServiceListener {
62         @RequiresApi(33)
onServiceConnected(int profile, BluetoothProfile proxy)63         public void onServiceConnected(int profile, BluetoothProfile proxy) {
64             if (VDBG) {
65                 Log.d(TAG, "Bluetooth service connected");
66             }
67             mService = (BluetoothCsipSetCoordinator) proxy;
68             // We just bound to the service, so refresh the UI for any connected CSIP devices.
69             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
70             while (!deviceList.isEmpty()) {
71                 BluetoothDevice nextDevice = deviceList.remove(0);
72                 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
73                 // we may add a new device here, but generally this should not happen
74                 if (device == null) {
75                     if (VDBG) {
76                         Log.d(TAG, "CsipSetCoordinatorProfile found new device: " + nextDevice);
77                     }
78                     device = mDeviceManager.addDevice(nextDevice);
79                 }
80                 device.onProfileStateChanged(
81                         CsipSetCoordinatorProfile.this, BluetoothProfile.STATE_CONNECTED);
82                 device.refresh();
83             }
84 
85             mDeviceManager.updateCsipDevices();
86             mProfileManager.callServiceConnectedListeners();
87             mIsProfileReady = true;
88         }
89 
onServiceDisconnected(int profile)90         public void onServiceDisconnected(int profile) {
91             if (VDBG) {
92                 Log.d(TAG, "Bluetooth service disconnected");
93             }
94             mProfileManager.callServiceDisconnectedListeners();
95             mIsProfileReady = false;
96         }
97     }
98 
CsipSetCoordinatorProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)99     CsipSetCoordinatorProfile(Context context, CachedBluetoothDeviceManager deviceManager,
100             LocalBluetoothProfileManager profileManager) {
101         mContext = context;
102         mDeviceManager = deviceManager;
103         mProfileManager = profileManager;
104 
105         BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
106                 new CoordinatedSetServiceListener(), BluetoothProfile.CSIP_SET_COORDINATOR);
107     }
108 
109     /**
110      * Get CSIP devices matching connection states{
111      *
112      * @code BluetoothProfile.STATE_CONNECTED,
113      * @code BluetoothProfile.STATE_CONNECTING,
114      * @code BluetoothProfile.STATE_DISCONNECTING}
115      *
116      * @return Matching device list
117      */
getConnectedDevices()118     public List<BluetoothDevice> getConnectedDevices() {
119         if (mService == null) {
120             return new ArrayList<BluetoothDevice>(0);
121         }
122         return mService.getDevicesMatchingConnectionStates(
123                 new int[] {BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
124                         BluetoothProfile.STATE_DISCONNECTING});
125     }
126 
127     /**
128      * Gets the connection status of the device.
129      *
130      * @code BluetoothProfile.STATE_CONNECTED,
131      * @code BluetoothProfile.STATE_CONNECTING,
132      * @code BluetoothProfile.STATE_DISCONNECTING}
133      *
134      * @return Connection status, {@code BluetoothProfile.STATE_DISCONNECTED} if unknown.
135      */
getConnectionStatus(BluetoothDevice device)136     public int getConnectionStatus(BluetoothDevice device) {
137         if (mService == null) {
138             return BluetoothProfile.STATE_DISCONNECTED;
139         }
140         return mService.getConnectionState(device);
141     }
142 
143     @Override
isProfileReady()144     public boolean isProfileReady() {
145         return mIsProfileReady;
146     }
147 
148     @Override
getProfileId()149     public int getProfileId() {
150         return BluetoothProfile.CSIP_SET_COORDINATOR;
151     }
152 
153     @Override
accessProfileEnabled()154     public boolean accessProfileEnabled() {
155         return false;
156     }
157 
158     @Override
isAutoConnectable()159     public boolean isAutoConnectable() {
160         return true;
161     }
162 
163     @Override
isEnabled(BluetoothDevice device)164     public boolean isEnabled(BluetoothDevice device) {
165         if (mService == null || device == null) {
166             return false;
167         }
168         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
169     }
170 
171     @Override
getConnectionPolicy(BluetoothDevice device)172     public int getConnectionPolicy(BluetoothDevice device) {
173         if (mService == null || device == null) {
174             return CONNECTION_POLICY_FORBIDDEN;
175         }
176         return mService.getConnectionPolicy(device);
177     }
178 
179     @Override
setEnabled(BluetoothDevice device, boolean enabled)180     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
181         boolean isSuccessful = false;
182         if (mService == null || device == null) {
183             return false;
184         }
185         if (enabled) {
186             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
187                 isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
188             }
189         } else {
190             isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
191         }
192 
193         return isSuccessful;
194     }
195 
196     @Override
getOrdinal()197     public int getOrdinal() {
198         return ORDINAL;
199     }
200 
201     @Override
getNameResource(BluetoothDevice device)202     public int getNameResource(BluetoothDevice device) {
203         return R.string.summary_empty;
204     }
205 
206     @Override
getSummaryResourceForDevice(BluetoothDevice device)207     public int getSummaryResourceForDevice(BluetoothDevice device) {
208         int state = getConnectionStatus(device);
209         return BluetoothUtils.getConnectionStateSummary(state);
210     }
211 
212     @Override
getDrawableResource(BluetoothClass btClass)213     public int getDrawableResource(BluetoothClass btClass) {
214         return 0;
215     }
216 
217     /**
218      * Get the device's groups and correspondsing uuids map.
219      * @param device the bluetooth device
220      * @return Map of groups ids and related UUIDs
221      */
getGroupUuidMapByDevice(BluetoothDevice device)222     public Map<Integer, ParcelUuid> getGroupUuidMapByDevice(BluetoothDevice device) {
223         if (mService == null || device == null) {
224             return null;
225         }
226         return mService.getGroupUuidMapByDevice(device);
227     }
228 
229     /**
230      * Return the profile name as a string.
231      */
toString()232     public String toString() {
233         return NAME;
234     }
235 
236     @RequiresApi(33)
finalize()237     protected void finalize() {
238         if (VDBG) {
239             Log.d(TAG, "finalize()");
240         }
241         if (mService != null) {
242             try {
243                 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
244                         BluetoothProfile.CSIP_SET_COORDINATOR, mService);
245                 mService = null;
246             } catch (Throwable t) {
247                 Log.w(TAG, "Error cleaning up CSIP Set Coordinator proxy", t);
248             }
249         }
250     }
251 }
252