1 /*
2  * Copyright (C) 2016 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 android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHeadsetClient;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.BluetoothUuid;
25 import android.content.Context;
26 import android.os.ParcelUuid;
27 import android.util.Log;
28 
29 import com.android.settingslib.R;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Handles the Handsfree HF role.
36  */
37 final class HfpClientProfile implements LocalBluetoothProfile {
38     private static final String TAG = "HfpClientProfile";
39 
40     private BluetoothHeadsetClient mService;
41     private boolean mIsProfileReady;
42 
43     private final CachedBluetoothDeviceManager mDeviceManager;
44 
45     static final ParcelUuid[] SRC_UUIDS = {
46         BluetoothUuid.HSP_AG,
47         BluetoothUuid.Handsfree_AG,
48     };
49 
50     static final String NAME = "HEADSET_CLIENT";
51     private final LocalBluetoothProfileManager mProfileManager;
52 
53     // Order of this profile in device profiles list
54     private static final int ORDINAL = 0;
55 
56     // These callbacks run on the main thread.
57     private final class HfpClientServiceListener
58             implements BluetoothProfile.ServiceListener {
59 
60         @Override
onServiceConnected(int profile, BluetoothProfile proxy)61         public void onServiceConnected(int profile, BluetoothProfile proxy) {
62             mService = (BluetoothHeadsetClient) proxy;
63             // We just bound to the service, so refresh the UI for any connected HFP devices.
64             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
65             while (!deviceList.isEmpty()) {
66                 BluetoothDevice nextDevice = deviceList.remove(0);
67                 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
68                 // we may add a new device here, but generally this should not happen
69                 if (device == null) {
70                     Log.w(TAG, "HfpClient profile found new device: " + nextDevice);
71                     device = mDeviceManager.addDevice(nextDevice);
72                 }
73                 device.onProfileStateChanged(
74                     HfpClientProfile.this, BluetoothProfile.STATE_CONNECTED);
75                 device.refresh();
76             }
77             mIsProfileReady=true;
78         }
79 
80         @Override
onServiceDisconnected(int profile)81         public void onServiceDisconnected(int profile) {
82             mIsProfileReady=false;
83         }
84     }
85 
86     @Override
isProfileReady()87     public boolean isProfileReady() {
88         return mIsProfileReady;
89     }
90 
91     @Override
getProfileId()92     public int getProfileId() {
93         return BluetoothProfile.HEADSET_CLIENT;
94     }
95 
HfpClientProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)96     HfpClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
97             LocalBluetoothProfileManager profileManager) {
98         mDeviceManager = deviceManager;
99         mProfileManager = profileManager;
100         BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
101                 new HfpClientServiceListener(), BluetoothProfile.HEADSET_CLIENT);
102     }
103 
104     @Override
accessProfileEnabled()105     public boolean accessProfileEnabled() {
106         return true;
107     }
108 
109     @Override
isAutoConnectable()110     public boolean isAutoConnectable() {
111         return true;
112     }
113 
getConnectedDevices()114     public List<BluetoothDevice> getConnectedDevices() {
115         if (mService == null) {
116             return new ArrayList<BluetoothDevice>(0);
117         }
118         return mService.getDevicesMatchingConnectionStates(
119               new int[] {BluetoothProfile.STATE_CONNECTED,
120                          BluetoothProfile.STATE_CONNECTING,
121                          BluetoothProfile.STATE_DISCONNECTING});
122     }
123 
124     @Override
connect(BluetoothDevice device)125     public boolean connect(BluetoothDevice device) {
126         if (mService == null) {
127             return false;
128         }
129         return mService.connect(device);
130     }
131 
132     @Override
disconnect(BluetoothDevice device)133     public boolean disconnect(BluetoothDevice device) {
134         if (mService == null) {
135             return false;
136         }
137         // Downgrade priority as user is disconnecting the headset.
138         if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
139             mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
140         }
141         return mService.disconnect(device);
142     }
143 
144     @Override
getConnectionStatus(BluetoothDevice device)145     public int getConnectionStatus(BluetoothDevice device) {
146         if (mService == null) {
147             return BluetoothProfile.STATE_DISCONNECTED;
148         }
149         return mService.getConnectionState(device);
150     }
151 
152     @Override
isPreferred(BluetoothDevice device)153     public boolean isPreferred(BluetoothDevice device) {
154         if (mService == null) {
155             return false;
156         }
157         return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
158     }
159 
160     @Override
getPreferred(BluetoothDevice device)161     public int getPreferred(BluetoothDevice device) {
162         if (mService == null) {
163             return BluetoothProfile.PRIORITY_OFF;
164         }
165         return mService.getPriority(device);
166     }
167 
168     @Override
setPreferred(BluetoothDevice device, boolean preferred)169     public void setPreferred(BluetoothDevice device, boolean preferred) {
170         if (mService == null) {
171             return;
172         }
173         if (preferred) {
174             if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
175                 mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
176             }
177         } else {
178             mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
179         }
180     }
181 
182     @Override
toString()183     public String toString() {
184         return NAME;
185     }
186 
187     @Override
getOrdinal()188     public int getOrdinal() {
189         return ORDINAL;
190     }
191 
192     @Override
getNameResource(BluetoothDevice device)193     public int getNameResource(BluetoothDevice device) {
194         return R.string.bluetooth_profile_headset;
195     }
196 
197     @Override
getSummaryResourceForDevice(BluetoothDevice device)198     public int getSummaryResourceForDevice(BluetoothDevice device) {
199         int state = getConnectionStatus(device);
200         switch (state) {
201             case BluetoothProfile.STATE_DISCONNECTED:
202                 return R.string.bluetooth_headset_profile_summary_use_for;
203 
204             case BluetoothProfile.STATE_CONNECTED:
205                 return R.string.bluetooth_headset_profile_summary_connected;
206 
207             default:
208                 return BluetoothUtils.getConnectionStateSummary(state);
209         }
210     }
211 
212     @Override
getDrawableResource(BluetoothClass btClass)213     public int getDrawableResource(BluetoothClass btClass) {
214         return com.android.internal.R.drawable.ic_bt_headset_hfp;
215     }
216 
finalize()217     protected void finalize() {
218         Log.d(TAG, "finalize()");
219         if (mService != null) {
220             try {
221                 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
222                     BluetoothProfile.HEADSET_CLIENT, mService);
223                 mService = null;
224             } catch (Throwable t) {
225                 Log.w(TAG, "Error cleaning up HfpClient proxy", t);
226             }
227         }
228     }
229 }
230