• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.settings.bluetooth;
18 
19 import com.android.settings.R;
20 
21 import android.bluetooth.BluetoothA2dp;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadset;
24 import android.bluetooth.BluetoothUuid;
25 import android.os.Handler;
26 import android.os.ParcelUuid;
27 import android.util.Log;
28 
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 
37 /**
38  * LocalBluetoothProfileManager is an abstract class defining the basic
39  * functionality related to a profile.
40  */
41 public abstract class LocalBluetoothProfileManager {
42     private static final String TAG = "LocalBluetoothProfileManager";
43 
44     /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] {
45         BluetoothUuid.HSP,
46         BluetoothUuid.Handsfree,
47     };
48 
49     /* package */ static final ParcelUuid[] A2DP_PROFILE_UUIDS = new ParcelUuid[] {
50         BluetoothUuid.AudioSink,
51         BluetoothUuid.AdvAudioDist,
52     };
53 
54     /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] {
55         BluetoothUuid.ObexObjectPush
56     };
57 
58     /**
59      * An interface for notifying BluetoothHeadset IPC clients when they have
60      * been connected to the BluetoothHeadset service.
61      */
62     public interface ServiceListener {
63         /**
64          * Called to notify the client when this proxy object has been
65          * connected to the BluetoothHeadset service. Clients must wait for
66          * this callback before making IPC calls on the BluetoothHeadset
67          * service.
68          */
onServiceConnected()69         public void onServiceConnected();
70 
71         /**
72          * Called to notify the client that this proxy object has been
73          * disconnected from the BluetoothHeadset service. Clients must not
74          * make IPC calls on the BluetoothHeadset service after this callback.
75          * This callback will currently only occur if the application hosting
76          * the BluetoothHeadset service, but may be called more often in future.
77          */
onServiceDisconnected()78         public void onServiceDisconnected();
79     }
80 
81     // TODO: close profiles when we're shutting down
82     private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
83             new HashMap<Profile, LocalBluetoothProfileManager>();
84 
85     protected LocalBluetoothManager mLocalManager;
86 
init(LocalBluetoothManager localManager)87     public static void init(LocalBluetoothManager localManager) {
88         synchronized (sProfileMap) {
89             if (sProfileMap.size() == 0) {
90                 LocalBluetoothProfileManager profileManager;
91 
92                 profileManager = new A2dpProfileManager(localManager);
93                 sProfileMap.put(Profile.A2DP, profileManager);
94 
95                 profileManager = new HeadsetProfileManager(localManager);
96                 sProfileMap.put(Profile.HEADSET, profileManager);
97 
98                 profileManager = new OppProfileManager(localManager);
99                 sProfileMap.put(Profile.OPP, profileManager);
100             }
101         }
102     }
103 
104     private static LinkedList<ServiceListener> mServiceListeners = new LinkedList<ServiceListener>();
105 
addServiceListener(ServiceListener l)106     public static void addServiceListener(ServiceListener l) {
107         mServiceListeners.add(l);
108     }
109 
removeServiceListener(ServiceListener l)110     public static void removeServiceListener(ServiceListener l) {
111         mServiceListeners.remove(l);
112     }
113 
isManagerReady()114     public static boolean isManagerReady() {
115         // Getting just the headset profile is fine for now. Will need to deal with A2DP
116         // and others if they aren't always in a ready state.
117         LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET);
118         if (profileManager == null) {
119             return sProfileMap.size() > 0;
120         }
121         return profileManager.isProfileReady();
122     }
123 
getProfileManager(LocalBluetoothManager localManager, Profile profile)124     public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
125             Profile profile) {
126         // Note: This code assumes that "localManager" is same as the
127         // LocalBluetoothManager that was used to initialize the sProfileMap.
128         // If that every changes, we can't just keep one copy of sProfileMap.
129         synchronized (sProfileMap) {
130             LocalBluetoothProfileManager profileManager = sProfileMap.get(profile);
131             if (profileManager == null) {
132                 Log.e(TAG, "profileManager can't be found for " + profile.toString());
133             }
134             return profileManager;
135         }
136     }
137 
138     /**
139      * Temporary method to fill profiles based on a device's class.
140      *
141      * NOTE: This list happens to define the connection order. We should put this logic in a more
142      * well known place when this method is no longer temporary.
143      * @param uuids of the remote device
144      * @param profiles The list of profiles to fill
145      */
updateProfiles(ParcelUuid[] uuids, List<Profile> profiles)146     public static void updateProfiles(ParcelUuid[] uuids, List<Profile> profiles) {
147         profiles.clear();
148 
149         if (uuids == null) {
150             return;
151         }
152 
153         if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS)) {
154             profiles.add(Profile.HEADSET);
155         }
156 
157         if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS)) {
158             profiles.add(Profile.A2DP);
159         }
160 
161         if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) {
162             profiles.add(Profile.OPP);
163         }
164     }
165 
LocalBluetoothProfileManager(LocalBluetoothManager localManager)166     protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
167         mLocalManager = localManager;
168     }
169 
getConnectedDevices()170     public abstract Set<BluetoothDevice> getConnectedDevices();
171 
connect(BluetoothDevice device)172     public abstract boolean connect(BluetoothDevice device);
173 
disconnect(BluetoothDevice device)174     public abstract boolean disconnect(BluetoothDevice device);
175 
getConnectionStatus(BluetoothDevice device)176     public abstract int getConnectionStatus(BluetoothDevice device);
177 
getSummary(BluetoothDevice device)178     public abstract int getSummary(BluetoothDevice device);
179 
convertState(int a2dpState)180     public abstract int convertState(int a2dpState);
181 
isPreferred(BluetoothDevice device)182     public abstract boolean isPreferred(BluetoothDevice device);
183 
getPreferred(BluetoothDevice device)184     public abstract int getPreferred(BluetoothDevice device);
185 
setPreferred(BluetoothDevice device, boolean preferred)186     public abstract void setPreferred(BluetoothDevice device, boolean preferred);
187 
isConnected(BluetoothDevice device)188     public boolean isConnected(BluetoothDevice device) {
189         return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device));
190     }
191 
isProfileReady()192     public abstract boolean isProfileReady();
193 
194     // TODO: int instead of enum
195     public enum Profile {
196         HEADSET(R.string.bluetooth_profile_headset),
197         A2DP(R.string.bluetooth_profile_a2dp),
198         OPP(R.string.bluetooth_profile_opp);
199 
200         public final int localizedString;
201 
Profile(int localizedString)202         private Profile(int localizedString) {
203             this.localizedString = localizedString;
204         }
205     }
206 
207     /**
208      * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
209      */
210     private static class A2dpProfileManager extends LocalBluetoothProfileManager {
211         private BluetoothA2dp mService;
212 
A2dpProfileManager(LocalBluetoothManager localManager)213         public A2dpProfileManager(LocalBluetoothManager localManager) {
214             super(localManager);
215             mService = new BluetoothA2dp(localManager.getContext());
216         }
217 
218         @Override
getConnectedDevices()219         public Set<BluetoothDevice> getConnectedDevices() {
220             return mService.getNonDisconnectedSinks();
221         }
222 
223         @Override
connect(BluetoothDevice device)224         public boolean connect(BluetoothDevice device) {
225             Set<BluetoothDevice> sinks = mService.getNonDisconnectedSinks();
226             if (sinks != null) {
227                 for (BluetoothDevice sink : sinks) {
228                     mService.disconnectSink(sink);
229                 }
230             }
231             return mService.connectSink(device);
232         }
233 
234         @Override
disconnect(BluetoothDevice device)235         public boolean disconnect(BluetoothDevice device) {
236             // Downgrade priority as user is disconnecting the sink.
237             if (mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_ON) {
238                 mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
239             }
240             return mService.disconnectSink(device);
241         }
242 
243         @Override
getConnectionStatus(BluetoothDevice device)244         public int getConnectionStatus(BluetoothDevice device) {
245             return convertState(mService.getSinkState(device));
246         }
247 
248         @Override
getSummary(BluetoothDevice device)249         public int getSummary(BluetoothDevice device) {
250             int connectionStatus = getConnectionStatus(device);
251 
252             if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
253                 return R.string.bluetooth_a2dp_profile_summary_connected;
254             } else {
255                 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
256             }
257         }
258 
259         @Override
isPreferred(BluetoothDevice device)260         public boolean isPreferred(BluetoothDevice device) {
261             return mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
262         }
263 
264         @Override
getPreferred(BluetoothDevice device)265         public int getPreferred(BluetoothDevice device) {
266             return mService.getSinkPriority(device);
267         }
268 
269         @Override
setPreferred(BluetoothDevice device, boolean preferred)270         public void setPreferred(BluetoothDevice device, boolean preferred) {
271             if (preferred) {
272                 if (mService.getSinkPriority(device) < BluetoothA2dp.PRIORITY_ON) {
273                     mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
274                 }
275             } else {
276                 mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_OFF);
277             }
278         }
279 
280         @Override
convertState(int a2dpState)281         public int convertState(int a2dpState) {
282             switch (a2dpState) {
283             case BluetoothA2dp.STATE_CONNECTED:
284                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
285             case BluetoothA2dp.STATE_CONNECTING:
286                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
287             case BluetoothA2dp.STATE_DISCONNECTED:
288                 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
289             case BluetoothA2dp.STATE_DISCONNECTING:
290                 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
291             case BluetoothA2dp.STATE_PLAYING:
292                 return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
293             default:
294                 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
295             }
296         }
297 
298         @Override
isProfileReady()299         public boolean isProfileReady() {
300             return true;
301         }
302     }
303 
304     /**
305      * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
306      */
307     private static class HeadsetProfileManager extends LocalBluetoothProfileManager
308             implements BluetoothHeadset.ServiceListener {
309         private BluetoothHeadset mService;
310         private Handler mUiHandler = new Handler();
311         private boolean profileReady = false;
312         private BluetoothDevice mDelayedConnectDevice = null;
313         private BluetoothDevice mDelayedDisconnectDevice = null;
314 
HeadsetProfileManager(LocalBluetoothManager localManager)315         public HeadsetProfileManager(LocalBluetoothManager localManager) {
316             super(localManager);
317             mService = new BluetoothHeadset(localManager.getContext(), this);
318         }
319 
onServiceConnected()320         public void onServiceConnected() {
321             profileReady = true;
322             // This could be called on a non-UI thread, funnel to UI thread.
323             // Delay for a few seconds to allow other proxies to connect.
324             mUiHandler.postDelayed(new Runnable() {
325                 public void run() {
326                     BluetoothDevice device = mService.getCurrentHeadset();
327 
328                     if (mDelayedConnectDevice != null) {
329                         Log.i(TAG, "service ready: connecting...");
330                         BluetoothDevice newDevice = mDelayedConnectDevice;
331                         mDelayedConnectDevice = null;
332 
333                         if (!newDevice.equals(device)) {
334                             if (device != null) {
335                                 Log.i(TAG, "disconnecting old headset");
336                                 mService.disconnectHeadset(device);
337                             }
338                             Log.i(TAG, "connecting to pending headset");
339                             mService.connectHeadset(newDevice);
340                         }
341                     } else if (mDelayedDisconnectDevice != null) {
342                         Log.i(TAG, "service ready: disconnecting...");
343                         if (mDelayedDisconnectDevice.equals(device)) {
344                             Log.i(TAG, "disconnecting headset");
345                             mService.disconnectHeadset(device);
346                         }
347                         mDelayedDisconnectDevice = null;
348                     } else {
349                         /*
350                          * We just bound to the service, so refresh the UI of the
351                          * headset device.
352                          */
353                         if (device == null) return;
354                         mLocalManager.getCachedDeviceManager()
355                             .onProfileStateChanged(device, Profile.HEADSET,
356                                                    BluetoothHeadset.STATE_CONNECTED);
357                     }
358                 }
359             }, 2000);  // wait 2 seconds for other proxies to connect
360 
361             if (mServiceListeners.size() > 0) {
362                 Iterator<ServiceListener> it = mServiceListeners.iterator();
363                 while(it.hasNext()) {
364                     it.next().onServiceConnected();
365                 }
366             }
367         }
368 
onServiceDisconnected()369         public void onServiceDisconnected() {
370             profileReady = false;
371             if (mServiceListeners.size() > 0) {
372                 Iterator<ServiceListener> it = mServiceListeners.iterator();
373                 while(it.hasNext()) {
374                     it.next().onServiceDisconnected();
375                 }
376             }
377         }
378 
379         @Override
isProfileReady()380         public boolean isProfileReady() {
381             return profileReady;
382         }
383 
384         @Override
getConnectedDevices()385         public Set<BluetoothDevice> getConnectedDevices() {
386             Set<BluetoothDevice> devices = null;
387             BluetoothDevice device = mService.getCurrentHeadset();
388             if (device != null) {
389                 devices = new HashSet<BluetoothDevice>();
390                 devices.add(device);
391             }
392             return devices;
393         }
394 
395         @Override
connect(BluetoothDevice device)396         public boolean connect(BluetoothDevice device) {
397             // Delay connection until onServiceConnected() if the
398             // manager isn't ready
399             if (!isManagerReady()) {
400                 Log.w(TAG, "HeadsetProfileManager delaying connect, "
401                         + "manager not ready");
402                 mDelayedConnectDevice = device;
403                 mDelayedDisconnectDevice = null;
404                 return true;  // hopefully it will succeed
405             }
406 
407             // Since connectHeadset fails if already connected to a headset, we
408             // disconnect from any headset first
409             BluetoothDevice currDevice = mService.getCurrentHeadset();
410             if (currDevice != null) {
411                 mService.disconnectHeadset(currDevice);
412             }
413             return mService.connectHeadset(device);
414         }
415 
416         @Override
disconnect(BluetoothDevice device)417         public boolean disconnect(BluetoothDevice device) {
418             // Delay connection until onServiceConnected() if the
419             // manager isn't ready
420             if (!isManagerReady()) {
421                 Log.w(TAG, "HeadsetProfileManager delaying disconnect, "
422                         + "manager not ready");
423                 mDelayedConnectDevice = null;
424                 mDelayedDisconnectDevice = device;
425                 return true;  // hopefully it will succeed
426             }
427 
428             BluetoothDevice currDevice = mService.getCurrentHeadset();
429             if (currDevice != null && currDevice.equals(device)) {
430                 // Downgrade prority as user is disconnecting the headset.
431                 if (mService.getPriority(device) > BluetoothHeadset.PRIORITY_ON) {
432                     mService.setPriority(device, BluetoothHeadset.PRIORITY_ON);
433                 }
434                 return mService.disconnectHeadset(device);
435             } else {
436                 return false;
437             }
438         }
439 
440         @Override
getConnectionStatus(BluetoothDevice device)441         public int getConnectionStatus(BluetoothDevice device) {
442             BluetoothDevice currentDevice = mService.getCurrentHeadset();
443             return currentDevice != null && currentDevice.equals(device)
444                     ? convertState(mService.getState(device))
445                     : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
446         }
447 
448         @Override
getSummary(BluetoothDevice device)449         public int getSummary(BluetoothDevice device) {
450             int connectionStatus = getConnectionStatus(device);
451 
452             if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
453                 return R.string.bluetooth_headset_profile_summary_connected;
454             } else {
455                 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
456             }
457         }
458 
459         @Override
isPreferred(BluetoothDevice device)460         public boolean isPreferred(BluetoothDevice device) {
461             return mService.getPriority(device) > BluetoothHeadset.PRIORITY_OFF;
462         }
463 
464         @Override
getPreferred(BluetoothDevice device)465         public int getPreferred(BluetoothDevice device) {
466             return mService.getPriority(device);
467         }
468 
469         @Override
setPreferred(BluetoothDevice device, boolean preferred)470         public void setPreferred(BluetoothDevice device, boolean preferred) {
471             if (preferred) {
472                 if (mService.getPriority(device) < BluetoothHeadset.PRIORITY_ON) {
473                     mService.setPriority(device, BluetoothHeadset.PRIORITY_ON);
474                 }
475             } else {
476                 mService.setPriority(device, BluetoothHeadset.PRIORITY_OFF);
477             }
478         }
479 
480         @Override
convertState(int headsetState)481         public int convertState(int headsetState) {
482             switch (headsetState) {
483             case BluetoothHeadset.STATE_CONNECTED:
484                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
485             case BluetoothHeadset.STATE_CONNECTING:
486                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
487             case BluetoothHeadset.STATE_DISCONNECTED:
488                 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
489             default:
490                 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
491             }
492         }
493     }
494 
495     /**
496      * OppProfileManager
497      */
498     private static class OppProfileManager extends LocalBluetoothProfileManager {
499 
OppProfileManager(LocalBluetoothManager localManager)500         public OppProfileManager(LocalBluetoothManager localManager) {
501             super(localManager);
502         }
503 
504         @Override
getConnectedDevices()505         public Set<BluetoothDevice> getConnectedDevices() {
506             return null;
507         }
508 
509         @Override
connect(BluetoothDevice device)510         public boolean connect(BluetoothDevice device) {
511             return false;
512         }
513 
514         @Override
disconnect(BluetoothDevice device)515         public boolean disconnect(BluetoothDevice device) {
516             return false;
517         }
518 
519         @Override
getConnectionStatus(BluetoothDevice device)520         public int getConnectionStatus(BluetoothDevice device) {
521             return -1;
522         }
523 
524         @Override
getSummary(BluetoothDevice device)525         public int getSummary(BluetoothDevice device) {
526             int connectionStatus = getConnectionStatus(device);
527 
528             if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
529                 return R.string.bluetooth_opp_profile_summary_connected;
530             } else {
531                 return R.string.bluetooth_opp_profile_summary_not_connected;
532             }
533         }
534 
535         @Override
isPreferred(BluetoothDevice device)536         public boolean isPreferred(BluetoothDevice device) {
537             return false;
538         }
539 
540         @Override
getPreferred(BluetoothDevice device)541         public int getPreferred(BluetoothDevice device) {
542             return -1;
543         }
544 
545         @Override
setPreferred(BluetoothDevice device, boolean preferred)546         public void setPreferred(BluetoothDevice device, boolean preferred) {
547         }
548 
549         @Override
isProfileReady()550         public boolean isProfileReady() {
551             return true;
552         }
553 
554         @Override
convertState(int oppState)555         public int convertState(int oppState) {
556             switch (oppState) {
557             case 0:
558                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
559             case 1:
560                 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
561             case 2:
562                 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
563             default:
564                 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
565             }
566         }
567     }
568 }
569