• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.bluetooth;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Message;
24 import android.bluetooth.BluetoothAdapter;
25 import android.os.PowerManager;
26 import android.server.BluetoothA2dpService;
27 import android.server.BluetoothService;
28 import android.util.Log;
29 import android.util.Pair;
30 
31 import com.android.internal.util.State;
32 import com.android.internal.util.StateMachine;
33 
34 import java.util.Set;
35 
36 /**
37  * This class is the Profile connection state machine associated with a remote
38  * device. When the device bonds an instance of this class is created.
39  * This tracks incoming and outgoing connections of all the profiles. Incoming
40  * connections are preferred over outgoing connections and HFP preferred over
41  * A2DP. When the device is unbonded, the instance is removed.
42  *
43  * States:
44  * {@link BondedDevice}: This state represents a bonded device. When in this
45  * state none of the profiles are in transition states.
46  *
47  * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
48  * state because of a outgoing Connect or Disconnect.
49  *
50  * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
51  * state because of a incoming Connect or Disconnect.
52  *
53  * {@link IncomingA2dp}: A2dp profile connection is in a transition
54  * state because of a incoming Connect or Disconnect.
55  *
56  * {@link OutgoingA2dp}: A2dp profile connection is in a transition
57  * state because of a outgoing Connect or Disconnect.
58  *
59  * Todo(): Write tests for this class, when the Android Mock support is completed.
60  * @hide
61  */
62 public final class BluetoothDeviceProfileState extends StateMachine {
63     private static final String TAG = "BluetoothDeviceProfileState";
64     private static final boolean DBG = false;
65 
66     // TODO(): Restructure the state machine to make it scalable with regard to profiles.
67     public static final int CONNECT_HFP_OUTGOING = 1;
68     public static final int CONNECT_HFP_INCOMING = 2;
69     public static final int CONNECT_A2DP_OUTGOING = 3;
70     public static final int CONNECT_A2DP_INCOMING = 4;
71     public static final int CONNECT_HID_OUTGOING = 5;
72     public static final int CONNECT_HID_INCOMING = 6;
73 
74     public static final int DISCONNECT_HFP_OUTGOING = 50;
75     private static final int DISCONNECT_HFP_INCOMING = 51;
76     public static final int DISCONNECT_A2DP_OUTGOING = 52;
77     public static final int DISCONNECT_A2DP_INCOMING = 53;
78     public static final int DISCONNECT_HID_OUTGOING = 54;
79     public static final int DISCONNECT_HID_INCOMING = 55;
80     public static final int DISCONNECT_PBAP_OUTGOING = 56;
81 
82     public static final int UNPAIR = 100;
83     public static final int AUTO_CONNECT_PROFILES = 101;
84     public static final int TRANSITION_TO_STABLE = 102;
85     public static final int CONNECT_OTHER_PROFILES = 103;
86     private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
87     private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
88 
89     public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
90     private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
91     private static final int CONNECTION_ACCESS_UNDEFINED = -1;
92     private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
93     private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
94 
95     private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
96     private static final String ACCESS_AUTHORITY_CLASS =
97         "com.android.settings.bluetooth.BluetoothPermissionRequest";
98 
99     private BondedDevice mBondedDevice = new BondedDevice();
100     private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
101     private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
102     private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
103     private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
104     private OutgoingHid mOutgoingHid = new OutgoingHid();
105     private IncomingHid mIncomingHid = new IncomingHid();
106 
107     private Context mContext;
108     private BluetoothService mService;
109     private BluetoothA2dpService mA2dpService;
110     private BluetoothHeadset  mHeadsetService;
111     private BluetoothPbap     mPbapService;
112     private PbapServiceListener mPbap;
113     private BluetoothAdapter mAdapter;
114     private boolean mPbapServiceConnected;
115     private boolean mAutoConnectionPending;
116     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
117 
118     private BluetoothDevice mDevice;
119     private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
120     private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED;
121     private long mIncomingRejectTimer;
122     private boolean mConnectionAccessReplyReceived = false;
123     private Pair<Integer, String> mIncomingConnections;
124     private PowerManager.WakeLock mWakeLock;
125     private PowerManager mPowerManager;
126     private boolean mPairingRequestRcvd = false;
127 
128     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
129         @Override
130         public void onReceive(Context context, Intent intent) {
131             String action = intent.getAction();
132             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
133             if (device == null || !device.equals(mDevice)) return;
134 
135             if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
136                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
137                 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
138                 // We trust this device now
139                 if (newState == BluetoothHeadset.STATE_CONNECTED) {
140                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
141                 }
142                 mA2dpState = newState;
143                 if (oldState == BluetoothA2dp.STATE_CONNECTED &&
144                     newState == BluetoothA2dp.STATE_DISCONNECTED) {
145                     sendMessage(DISCONNECT_A2DP_INCOMING);
146                 }
147                 if (newState == BluetoothProfile.STATE_CONNECTED ||
148                     newState == BluetoothProfile.STATE_DISCONNECTED) {
149                     sendMessage(TRANSITION_TO_STABLE);
150                 }
151             } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
152                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
153                 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
154                 // We trust this device now
155                 if (newState == BluetoothHeadset.STATE_CONNECTED) {
156                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
157                 }
158                 mHeadsetState = newState;
159                 if (oldState == BluetoothHeadset.STATE_CONNECTED &&
160                     newState == BluetoothHeadset.STATE_DISCONNECTED) {
161                     sendMessage(DISCONNECT_HFP_INCOMING);
162                 }
163                 if (newState == BluetoothProfile.STATE_CONNECTED ||
164                     newState == BluetoothProfile.STATE_DISCONNECTED) {
165                     sendMessage(TRANSITION_TO_STABLE);
166                 }
167             } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
168                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
169                 int oldState =
170                     intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
171                 // We trust this device now
172                 if (newState == BluetoothHeadset.STATE_CONNECTED) {
173                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
174                 }
175                 if (oldState == BluetoothProfile.STATE_CONNECTED &&
176                     newState == BluetoothProfile.STATE_DISCONNECTED) {
177                     sendMessage(DISCONNECT_HID_INCOMING);
178                 }
179                 if (newState == BluetoothProfile.STATE_CONNECTED ||
180                     newState == BluetoothProfile.STATE_DISCONNECTED) {
181                     sendMessage(TRANSITION_TO_STABLE);
182                 }
183             } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
184                 // This is technically not needed, but we can get stuck sometimes.
185                 // For example, if incoming A2DP fails, we are not informed by Bluez
186                 sendMessage(TRANSITION_TO_STABLE);
187             } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
188                 mWakeLock.release();
189                 int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
190                                              BluetoothDevice.CONNECTION_ACCESS_NO);
191                 Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
192                 msg.arg1 = val;
193                 sendMessage(msg);
194             } else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
195                 mPairingRequestRcvd = true;
196             } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
197                 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
198                         BluetoothDevice.ERROR);
199                 if (state == BluetoothDevice.BOND_BONDED && mPairingRequestRcvd) {
200                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
201                     mPairingRequestRcvd = false;
202                 } else if (state == BluetoothDevice.BOND_NONE) {
203                     mPairingRequestRcvd = false;
204                 }
205             }
206         }
207     };
208 
isPhoneDocked(BluetoothDevice autoConnectDevice)209     private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
210         // This works only because these broadcast intents are "sticky"
211         Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
212         if (i != null) {
213             int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
214             if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
215                 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
216                 if (device != null && autoConnectDevice.equals(device)) {
217                     return true;
218                 }
219             }
220         }
221         return false;
222     }
223 
BluetoothDeviceProfileState(Context context, String address, BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust)224     public BluetoothDeviceProfileState(Context context, String address,
225           BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust) {
226         super(address);
227         mContext = context;
228         mDevice = new BluetoothDevice(address);
229         mService = service;
230         mA2dpService = a2dpService;
231 
232         addState(mBondedDevice);
233         addState(mOutgoingHandsfree);
234         addState(mIncomingHandsfree);
235         addState(mIncomingA2dp);
236         addState(mOutgoingA2dp);
237         addState(mOutgoingHid);
238         addState(mIncomingHid);
239         setInitialState(mBondedDevice);
240 
241         IntentFilter filter = new IntentFilter();
242         // Fine-grained state broadcasts
243         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
244         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
245         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
246         filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
247         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
248         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
249         filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
250         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
251 
252         mContext.registerReceiver(mBroadcastReceiver, filter);
253 
254         mAdapter = BluetoothAdapter.getDefaultAdapter();
255         mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
256                                 BluetoothProfile.HEADSET);
257         // TODO(): Convert PBAP to the new Profile APIs.
258         mPbap = new PbapServiceListener();
259 
260         mIncomingConnections = mService.getIncomingState(address);
261         mIncomingRejectTimer = readTimerValue();
262         mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
263         mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
264                                               PowerManager.ACQUIRE_CAUSES_WAKEUP |
265                                               PowerManager.ON_AFTER_RELEASE, TAG);
266         mWakeLock.setReferenceCounted(false);
267 
268         if (setTrust) {
269             setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
270         }
271     }
272 
273     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
274         new BluetoothProfile.ServiceListener() {
275         public void onServiceConnected(int profile, BluetoothProfile proxy) {
276             synchronized(BluetoothDeviceProfileState.this) {
277                 mHeadsetService = (BluetoothHeadset) proxy;
278                 if (mAutoConnectionPending) {
279                     sendMessage(AUTO_CONNECT_PROFILES);
280                     mAutoConnectionPending = false;
281                 }
282             }
283         }
284         public void onServiceDisconnected(int profile) {
285             synchronized(BluetoothDeviceProfileState.this) {
286                 mHeadsetService = null;
287             }
288         }
289     };
290 
291     private class PbapServiceListener implements BluetoothPbap.ServiceListener {
PbapServiceListener()292         public PbapServiceListener() {
293             mPbapService = new BluetoothPbap(mContext, this);
294         }
onServiceConnected()295         public void onServiceConnected() {
296             synchronized(BluetoothDeviceProfileState.this) {
297                 mPbapServiceConnected = true;
298             }
299         }
onServiceDisconnected()300         public void onServiceDisconnected() {
301             synchronized(BluetoothDeviceProfileState.this) {
302                 mPbapServiceConnected = false;
303             }
304         }
305     }
306 
307     private class BondedDevice extends State {
308         @Override
enter()309         public void enter() {
310             Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
311             Message m = new Message();
312             m.copyFrom(getCurrentMessage());
313             sendMessageAtFrontOfQueue(m);
314         }
315         @Override
processMessage(Message message)316         public boolean processMessage(Message message) {
317             log("ACL Connected State -> Processing Message: " + message.what);
318             switch(message.what) {
319                 case CONNECT_HFP_OUTGOING:
320                 case DISCONNECT_HFP_OUTGOING:
321                     transitionTo(mOutgoingHandsfree);
322                     break;
323                 case CONNECT_HFP_INCOMING:
324                     transitionTo(mIncomingHandsfree);
325                     break;
326                 case DISCONNECT_HFP_INCOMING:
327                     transitionTo(mIncomingHandsfree);
328                     break;
329                 case CONNECT_A2DP_OUTGOING:
330                 case DISCONNECT_A2DP_OUTGOING:
331                     transitionTo(mOutgoingA2dp);
332                     break;
333                 case CONNECT_A2DP_INCOMING:
334                 case DISCONNECT_A2DP_INCOMING:
335                     transitionTo(mIncomingA2dp);
336                     break;
337                 case CONNECT_HID_OUTGOING:
338                 case DISCONNECT_HID_OUTGOING:
339                     transitionTo(mOutgoingHid);
340                     break;
341                 case CONNECT_HID_INCOMING:
342                 case DISCONNECT_HID_INCOMING:
343                     transitionTo(mIncomingHid);
344                     break;
345                 case DISCONNECT_PBAP_OUTGOING:
346                     processCommand(DISCONNECT_PBAP_OUTGOING);
347                     break;
348                 case UNPAIR:
349                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
350                         sendMessage(DISCONNECT_HFP_OUTGOING);
351                         deferMessage(message);
352                         break;
353                     } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
354                         sendMessage(DISCONNECT_A2DP_OUTGOING);
355                         deferMessage(message);
356                         break;
357                     } else if (mService.getInputDeviceConnectionState(mDevice) !=
358                             BluetoothInputDevice.STATE_DISCONNECTED) {
359                         sendMessage(DISCONNECT_HID_OUTGOING);
360                         deferMessage(message);
361                         break;
362                     }
363                     processCommand(UNPAIR);
364                     break;
365                 case AUTO_CONNECT_PROFILES:
366                     if (isPhoneDocked(mDevice)) {
367                         // Don't auto connect to docks.
368                         break;
369                     } else {
370                         if (mHeadsetService == null) {
371                               mAutoConnectionPending = true;
372                         } else if (mHeadsetService.getPriority(mDevice) ==
373                               BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
374                               mHeadsetService.getDevicesMatchingConnectionStates(
375                                   new int[] {BluetoothProfile.STATE_CONNECTED,
376                                              BluetoothProfile.STATE_CONNECTING,
377                                              BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
378                             mHeadsetService.connect(mDevice);
379                         }
380                         if (mA2dpService != null &&
381                               mA2dpService.getPriority(mDevice) ==
382                               BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
383                               mA2dpService.getDevicesMatchingConnectionStates(
384                                   new int[] {BluetoothA2dp.STATE_CONNECTED,
385                                              BluetoothProfile.STATE_CONNECTING,
386                                              BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
387                             mA2dpService.connect(mDevice);
388                         }
389                         if (mService.getInputDevicePriority(mDevice) ==
390                               BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
391                             mService.connectInputDevice(mDevice);
392                         }
393                     }
394                     break;
395                 case CONNECT_OTHER_PROFILES:
396                     if (isPhoneDocked(mDevice)) {
397                        break;
398                     }
399                     if (message.arg1 == CONNECT_A2DP_OUTGOING) {
400                         if (mA2dpService != null &&
401                             mA2dpService.getConnectedDevices().size() == 0) {
402                             Log.i(TAG, "A2dp:Connect Other Profiles");
403                             mA2dpService.connect(mDevice);
404                         }
405                     } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
406                         if (mHeadsetService == null) {
407                             deferMessage(message);
408                         } else {
409                             if (mHeadsetService.getConnectedDevices().size() == 0) {
410                                 Log.i(TAG, "Headset:Connect Other Profiles");
411                                 mHeadsetService.connect(mDevice);
412                             }
413                         }
414                     }
415                     break;
416                 case TRANSITION_TO_STABLE:
417                     // ignore.
418                     break;
419                 case SM_QUIT_CMD:
420                     mContext.unregisterReceiver(mBroadcastReceiver);
421                     mBroadcastReceiver = null;
422                     mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetService);
423                     mBluetoothProfileServiceListener = null;
424                     mOutgoingHandsfree = null;
425                     mPbap = null;
426                     mPbapService.close();
427                     mPbapService = null;
428                     mIncomingHid = null;
429                     mOutgoingHid = null;
430                     mIncomingHandsfree = null;
431                     mOutgoingHandsfree = null;
432                     mIncomingA2dp = null;
433                     mOutgoingA2dp = null;
434                     mBondedDevice = null;
435                     // There is a problem in the State Machine code
436                     // where things are not cleaned up properly, when quit message
437                     // is handled so return NOT_HANDLED as a workaround.
438                     return NOT_HANDLED;
439                 default:
440                     return NOT_HANDLED;
441             }
442             return HANDLED;
443         }
444     }
445 
446     private class OutgoingHandsfree extends State {
447         private boolean mStatus = false;
448         private int mCommand;
449 
450         @Override
enter()451         public void enter() {
452             Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
453             mCommand = getCurrentMessage().what;
454             if (mCommand != CONNECT_HFP_OUTGOING &&
455                 mCommand != DISCONNECT_HFP_OUTGOING) {
456                 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
457             }
458             mStatus = processCommand(mCommand);
459             if (!mStatus) {
460                 sendMessage(TRANSITION_TO_STABLE);
461                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
462                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
463             }
464         }
465 
466         @Override
processMessage(Message message)467         public boolean processMessage(Message message) {
468             log("OutgoingHandsfree State -> Processing Message: " + message.what);
469             Message deferMsg = new Message();
470             int command = message.what;
471             switch(command) {
472                 case CONNECT_HFP_OUTGOING:
473                     if (command != mCommand) {
474                         // Disconnect followed by a connect - defer
475                         deferMessage(message);
476                     }
477                     break;
478                 case CONNECT_HFP_INCOMING:
479                     if (mCommand == CONNECT_HFP_OUTGOING) {
480                         // Cancel outgoing connect, accept incoming
481                         cancelCommand(CONNECT_HFP_OUTGOING);
482                         transitionTo(mIncomingHandsfree);
483                     } else {
484                         // We have done the disconnect but we are not
485                         // sure which state we are in at this point.
486                         deferMessage(message);
487                     }
488                     break;
489                 case CONNECT_A2DP_INCOMING:
490                     // accept incoming A2DP, retry HFP_OUTGOING
491                     transitionTo(mIncomingA2dp);
492 
493                     if (mStatus) {
494                         deferMsg.what = mCommand;
495                         deferMessage(deferMsg);
496                     }
497                     break;
498                 case CONNECT_A2DP_OUTGOING:
499                     deferMessage(message);
500                     break;
501                 case DISCONNECT_HFP_OUTGOING:
502                     if (mCommand == CONNECT_HFP_OUTGOING) {
503                         // Cancel outgoing connect
504                         cancelCommand(CONNECT_HFP_OUTGOING);
505                         processCommand(DISCONNECT_HFP_OUTGOING);
506                     }
507                     // else ignore
508                     break;
509                 case DISCONNECT_HFP_INCOMING:
510                     // When this happens the socket would be closed and the headset
511                     // state moved to DISCONNECTED, cancel the outgoing thread.
512                     // if it still is in CONNECTING state
513                     cancelCommand(CONNECT_HFP_OUTGOING);
514                     break;
515                 case DISCONNECT_A2DP_OUTGOING:
516                     deferMessage(message);
517                     break;
518                 case DISCONNECT_A2DP_INCOMING:
519                     // Bluez will handle the disconnect. If because of this the outgoing
520                     // handsfree connection has failed, then retry.
521                     if (mStatus) {
522                        deferMsg.what = mCommand;
523                        deferMessage(deferMsg);
524                     }
525                     break;
526                 case CONNECT_HID_OUTGOING:
527                 case DISCONNECT_HID_OUTGOING:
528                     deferMessage(message);
529                     break;
530                 case CONNECT_HID_INCOMING:
531                     transitionTo(mIncomingHid);
532                     if (mStatus) {
533                         deferMsg.what = mCommand;
534                         deferMessage(deferMsg);
535                     }
536                     break;
537                 case DISCONNECT_HID_INCOMING:
538                     if (mStatus) {
539                         deferMsg.what = mCommand;
540                         deferMessage(deferMsg);
541                     }
542                     break; // ignore
543                 case DISCONNECT_PBAP_OUTGOING:
544                 case UNPAIR:
545                 case AUTO_CONNECT_PROFILES:
546                 case CONNECT_OTHER_PROFILES:
547                     deferMessage(message);
548                     break;
549                 case TRANSITION_TO_STABLE:
550                     transitionTo(mBondedDevice);
551                     break;
552                 default:
553                     return NOT_HANDLED;
554             }
555             return HANDLED;
556         }
557     }
558 
559     private class IncomingHandsfree extends State {
560         private boolean mStatus = false;
561         private int mCommand;
562 
563         @Override
enter()564         public void enter() {
565             Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
566             mCommand = getCurrentMessage().what;
567             if (mCommand != CONNECT_HFP_INCOMING &&
568                 mCommand != DISCONNECT_HFP_INCOMING) {
569                 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
570             }
571             mStatus = processCommand(mCommand);
572             if (!mStatus) {
573                 sendMessage(TRANSITION_TO_STABLE);
574                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
575                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
576             }
577         }
578 
579         @Override
processMessage(Message message)580         public boolean processMessage(Message message) {
581             log("IncomingHandsfree State -> Processing Message: " + message.what);
582             switch(message.what) {
583                 case CONNECT_HFP_OUTGOING:
584                     deferMessage(message);
585                     break;
586                 case CONNECT_HFP_INCOMING:
587                     // Ignore
588                     Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
589                     break;
590                 case CONNECTION_ACCESS_REQUEST_REPLY:
591                     int val = message.arg1;
592                     mConnectionAccessReplyReceived = true;
593                     boolean value = false;
594                     if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
595                         value = true;
596                     }
597                     setTrust(val);
598 
599                     handleIncomingConnection(CONNECT_HFP_INCOMING, value);
600                     break;
601                 case CONNECTION_ACCESS_REQUEST_EXPIRY:
602                     if (!mConnectionAccessReplyReceived) {
603                         handleIncomingConnection(CONNECT_HFP_INCOMING, false);
604                         sendConnectionAccessRemovalIntent();
605                         sendMessage(TRANSITION_TO_STABLE);
606                     }
607                     break;
608                 case CONNECT_A2DP_INCOMING:
609                     // Serialize the commands.
610                     deferMessage(message);
611                     break;
612                 case CONNECT_A2DP_OUTGOING:
613                     deferMessage(message);
614                     break;
615                 case DISCONNECT_HFP_OUTGOING:
616                     // We don't know at what state we are in the incoming HFP connection state.
617                     // We can be changing from DISCONNECTED to CONNECTING, or
618                     // from CONNECTING to CONNECTED, so serializing this command is
619                     // the safest option.
620                     deferMessage(message);
621                     break;
622                 case DISCONNECT_HFP_INCOMING:
623                     // Nothing to do here, we will already be DISCONNECTED
624                     // by this point.
625                     break;
626                 case DISCONNECT_A2DP_OUTGOING:
627                     deferMessage(message);
628                     break;
629                 case DISCONNECT_A2DP_INCOMING:
630                     // Bluez handles incoming A2DP disconnect.
631                     // If this causes incoming HFP to fail, it is more of a headset problem
632                     // since both connections are incoming ones.
633                     break;
634                 case CONNECT_HID_OUTGOING:
635                 case DISCONNECT_HID_OUTGOING:
636                     deferMessage(message);
637                     break;
638                 case CONNECT_HID_INCOMING:
639                 case DISCONNECT_HID_INCOMING:
640                      break; // ignore
641                 case DISCONNECT_PBAP_OUTGOING:
642                 case UNPAIR:
643                 case AUTO_CONNECT_PROFILES:
644                 case CONNECT_OTHER_PROFILES:
645                     deferMessage(message);
646                     break;
647                 case TRANSITION_TO_STABLE:
648                     transitionTo(mBondedDevice);
649                     break;
650                 default:
651                     return NOT_HANDLED;
652             }
653             return HANDLED;
654         }
655     }
656 
657     private class OutgoingA2dp extends State {
658         private boolean mStatus = false;
659         private int mCommand;
660 
661         @Override
enter()662         public void enter() {
663             Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
664             mCommand = getCurrentMessage().what;
665             if (mCommand != CONNECT_A2DP_OUTGOING &&
666                 mCommand != DISCONNECT_A2DP_OUTGOING) {
667                 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
668             }
669             mStatus = processCommand(mCommand);
670             if (!mStatus) {
671                 sendMessage(TRANSITION_TO_STABLE);
672                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
673                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
674             }
675         }
676 
677         @Override
processMessage(Message message)678         public boolean processMessage(Message message) {
679             log("OutgoingA2dp State->Processing Message: " + message.what);
680             Message deferMsg = new Message();
681             switch(message.what) {
682                 case CONNECT_HFP_OUTGOING:
683                     processCommand(CONNECT_HFP_OUTGOING);
684 
685                     // Don't cancel A2DP outgoing as there is no guarantee it
686                     // will get canceled.
687                     // It might already be connected but we might not have got the
688                     // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
689                     // The worst case, the connection will fail, retry.
690                     // The same applies to Disconnecting an A2DP connection.
691                     if (mStatus) {
692                         deferMsg.what = mCommand;
693                         deferMessage(deferMsg);
694                     }
695                     break;
696                 case CONNECT_HFP_INCOMING:
697                     processCommand(CONNECT_HFP_INCOMING);
698 
699                     // Don't cancel A2DP outgoing as there is no guarantee
700                     // it will get canceled.
701                     // The worst case, the connection will fail, retry.
702                     if (mStatus) {
703                         deferMsg.what = mCommand;
704                         deferMessage(deferMsg);
705                     }
706                     break;
707                 case CONNECT_A2DP_INCOMING:
708                     // Bluez will take care of conflicts between incoming and outgoing
709                     // connections.
710                     transitionTo(mIncomingA2dp);
711                     break;
712                 case CONNECT_A2DP_OUTGOING:
713                     // Ignore
714                     break;
715                 case DISCONNECT_HFP_OUTGOING:
716                     deferMessage(message);
717                     break;
718                 case DISCONNECT_HFP_INCOMING:
719                     // At this point, we are already disconnected
720                     // with HFP. Sometimes A2DP connection can
721                     // fail due to the disconnection of HFP. So add a retry
722                     // for the A2DP.
723                     if (mStatus) {
724                         deferMsg.what = mCommand;
725                         deferMessage(deferMsg);
726                     }
727                     break;
728                 case DISCONNECT_A2DP_OUTGOING:
729                     deferMessage(message);
730                     break;
731                 case DISCONNECT_A2DP_INCOMING:
732                     // Ignore, will be handled by Bluez
733                     break;
734                 case CONNECT_HID_OUTGOING:
735                 case DISCONNECT_HID_OUTGOING:
736                     deferMessage(message);
737                     break;
738                 case CONNECT_HID_INCOMING:
739                     transitionTo(mIncomingHid);
740                     if (mStatus) {
741                         deferMsg.what = mCommand;
742                         deferMessage(deferMsg);
743                     }
744                     break;
745                 case DISCONNECT_HID_INCOMING:
746                     if (mStatus) {
747                         deferMsg.what = mCommand;
748                         deferMessage(deferMsg);
749                     }
750                     break; // ignore
751                 case DISCONNECT_PBAP_OUTGOING:
752                 case UNPAIR:
753                 case AUTO_CONNECT_PROFILES:
754                 case CONNECT_OTHER_PROFILES:
755                     deferMessage(message);
756                     break;
757                 case TRANSITION_TO_STABLE:
758                     transitionTo(mBondedDevice);
759                     break;
760                 default:
761                     return NOT_HANDLED;
762             }
763             return HANDLED;
764         }
765     }
766 
767     private class IncomingA2dp extends State {
768         private boolean mStatus = false;
769         private int mCommand;
770 
771         @Override
enter()772         public void enter() {
773             Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
774             mCommand = getCurrentMessage().what;
775             if (mCommand != CONNECT_A2DP_INCOMING &&
776                 mCommand != DISCONNECT_A2DP_INCOMING) {
777                 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
778             }
779             mStatus = processCommand(mCommand);
780             if (!mStatus) {
781                 sendMessage(TRANSITION_TO_STABLE);
782                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
783                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
784             }
785         }
786 
787         @Override
processMessage(Message message)788         public boolean processMessage(Message message) {
789             log("IncomingA2dp State->Processing Message: " + message.what);
790             switch(message.what) {
791                 case CONNECT_HFP_OUTGOING:
792                     deferMessage(message);
793                     break;
794                 case CONNECT_HFP_INCOMING:
795                     // Shouldn't happen, but serialize the commands.
796                     deferMessage(message);
797                     break;
798                 case CONNECT_A2DP_INCOMING:
799                     // ignore
800                     break;
801                 case CONNECTION_ACCESS_REQUEST_REPLY:
802                     int val = message.arg1;
803                     mConnectionAccessReplyReceived = true;
804                     boolean value = false;
805                     if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
806                         value = true;
807                     }
808                     setTrust(val);
809                     handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
810                     break;
811                 case CONNECTION_ACCESS_REQUEST_EXPIRY:
812                     // The check protects the race condition between REQUEST_REPLY
813                     // and the timer expiry.
814                     if (!mConnectionAccessReplyReceived) {
815                         handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
816                         sendConnectionAccessRemovalIntent();
817                         sendMessage(TRANSITION_TO_STABLE);
818                     }
819                     break;
820                 case CONNECT_A2DP_OUTGOING:
821                     // Defer message and retry
822                     deferMessage(message);
823                     break;
824                 case DISCONNECT_HFP_OUTGOING:
825                     deferMessage(message);
826                     break;
827                 case DISCONNECT_HFP_INCOMING:
828                     // Shouldn't happen but if does, we can handle it.
829                     // Depends if the headset can handle it.
830                     // Incoming A2DP will be handled by Bluez, Disconnect HFP
831                     // the socket would have already been closed.
832                     // ignore
833                     break;
834                 case DISCONNECT_A2DP_OUTGOING:
835                     deferMessage(message);
836                     break;
837                 case DISCONNECT_A2DP_INCOMING:
838                     // Ignore, will be handled by Bluez
839                     break;
840                 case CONNECT_HID_OUTGOING:
841                 case DISCONNECT_HID_OUTGOING:
842                     deferMessage(message);
843                     break;
844                 case CONNECT_HID_INCOMING:
845                 case DISCONNECT_HID_INCOMING:
846                      break; // ignore
847                 case DISCONNECT_PBAP_OUTGOING:
848                 case UNPAIR:
849                 case AUTO_CONNECT_PROFILES:
850                 case CONNECT_OTHER_PROFILES:
851                     deferMessage(message);
852                     break;
853                 case TRANSITION_TO_STABLE:
854                     transitionTo(mBondedDevice);
855                     break;
856                 default:
857                     return NOT_HANDLED;
858             }
859             return HANDLED;
860         }
861     }
862 
863 
864     private class OutgoingHid extends State {
865         private boolean mStatus = false;
866         private int mCommand;
867 
868         @Override
enter()869         public void enter() {
870             log("Entering OutgoingHid state with: " + getCurrentMessage().what);
871             mCommand = getCurrentMessage().what;
872             if (mCommand != CONNECT_HID_OUTGOING &&
873                 mCommand != DISCONNECT_HID_OUTGOING) {
874                 Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
875             }
876             mStatus = processCommand(mCommand);
877             if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
878         }
879 
880         @Override
processMessage(Message message)881         public boolean processMessage(Message message) {
882             log("OutgoingHid State->Processing Message: " + message.what);
883             Message deferMsg = new Message();
884             switch(message.what) {
885                 // defer all outgoing messages
886                 case CONNECT_HFP_OUTGOING:
887                 case CONNECT_A2DP_OUTGOING:
888                 case CONNECT_HID_OUTGOING:
889                 case DISCONNECT_HFP_OUTGOING:
890                 case DISCONNECT_A2DP_OUTGOING:
891                 case DISCONNECT_HID_OUTGOING:
892                     deferMessage(message);
893                     break;
894 
895                 case CONNECT_HFP_INCOMING:
896                     transitionTo(mIncomingHandsfree);
897                     break;
898                 case CONNECT_A2DP_INCOMING:
899                     transitionTo(mIncomingA2dp);
900 
901                     // Don't cancel HID outgoing as there is no guarantee it
902                     // will get canceled.
903                     // It might already be connected but we might not have got the
904                     // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
905                     // The worst case, the connection will fail, retry.
906                     if (mStatus) {
907                         deferMsg.what = mCommand;
908                         deferMessage(deferMsg);
909                     }
910                     break;
911                 case CONNECT_HID_INCOMING:
912                   // Bluez will take care of the conflicts
913                     transitionTo(mIncomingHid);
914                     break;
915 
916                 case DISCONNECT_HFP_INCOMING:
917                 case DISCONNECT_A2DP_INCOMING:
918                     // At this point, we are already disconnected
919                     // with HFP. Sometimes HID connection can
920                     // fail due to the disconnection of HFP. So add a retry
921                     // for the HID.
922                     if (mStatus) {
923                         deferMsg.what = mCommand;
924                         deferMessage(deferMsg);
925                     }
926                     break;
927                 case DISCONNECT_HID_INCOMING:
928                     // Ignore, will be handled by Bluez
929                     break;
930                 case DISCONNECT_PBAP_OUTGOING:
931                 case UNPAIR:
932                 case AUTO_CONNECT_PROFILES:
933                     deferMessage(message);
934                     break;
935                 case TRANSITION_TO_STABLE:
936                     transitionTo(mBondedDevice);
937                     break;
938                 default:
939                     return NOT_HANDLED;
940             }
941             return HANDLED;
942         }
943     }
944 
945   private class IncomingHid extends State {
946       private boolean mStatus = false;
947       private int mCommand;
948 
949       @Override
enter()950     public void enter() {
951           log("Entering IncomingHid state with: " + getCurrentMessage().what);
952           mCommand = getCurrentMessage().what;
953           if (mCommand != CONNECT_HID_INCOMING &&
954               mCommand != DISCONNECT_HID_INCOMING) {
955               Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
956           }
957           mStatus = processCommand(mCommand);
958           if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
959       }
960 
961       @Override
processMessage(Message message)962     public boolean processMessage(Message message) {
963           log("IncomingHid State->Processing Message: " + message.what);
964           Message deferMsg = new Message();
965           switch(message.what) {
966               case CONNECT_HFP_OUTGOING:
967               case CONNECT_HFP_INCOMING:
968               case DISCONNECT_HFP_OUTGOING:
969               case CONNECT_A2DP_INCOMING:
970               case CONNECT_A2DP_OUTGOING:
971               case DISCONNECT_A2DP_OUTGOING:
972               case CONNECT_HID_OUTGOING:
973               case CONNECT_HID_INCOMING:
974               case DISCONNECT_HID_OUTGOING:
975                   deferMessage(message);
976                   break;
977               case CONNECTION_ACCESS_REQUEST_REPLY:
978                   mConnectionAccessReplyReceived = true;
979                   int val = message.arg1;
980                   setTrust(val);
981                   handleIncomingConnection(CONNECT_HID_INCOMING,
982                       val == BluetoothDevice.CONNECTION_ACCESS_YES);
983                   break;
984               case CONNECTION_ACCESS_REQUEST_EXPIRY:
985                   if (!mConnectionAccessReplyReceived) {
986                       handleIncomingConnection(CONNECT_HID_INCOMING, false);
987                       sendConnectionAccessRemovalIntent();
988                       sendMessage(TRANSITION_TO_STABLE);
989                   }
990                   break;
991               case DISCONNECT_HFP_INCOMING:
992                   // Shouldn't happen but if does, we can handle it.
993                   // Depends if the headset can handle it.
994                   // Incoming HID will be handled by Bluez, Disconnect HFP
995                   // the socket would have already been closed.
996                   // ignore
997                   break;
998               case DISCONNECT_HID_INCOMING:
999               case DISCONNECT_A2DP_INCOMING:
1000                   // Ignore, will be handled by Bluez
1001                   break;
1002               case DISCONNECT_PBAP_OUTGOING:
1003               case UNPAIR:
1004               case AUTO_CONNECT_PROFILES:
1005                   deferMessage(message);
1006                   break;
1007               case TRANSITION_TO_STABLE:
1008                   transitionTo(mBondedDevice);
1009                   break;
1010               default:
1011                   return NOT_HANDLED;
1012           }
1013           return HANDLED;
1014       }
1015   }
1016 
1017 
cancelCommand(int command)1018     synchronized void cancelCommand(int command) {
1019         if (command == CONNECT_HFP_OUTGOING ) {
1020             // Cancel the outgoing thread.
1021             if (mHeadsetService != null) {
1022                 mHeadsetService.cancelConnectThread();
1023             }
1024             // HeadsetService is down. Phone process most likely crashed.
1025             // The thread would have got killed.
1026         }
1027     }
1028 
deferProfileServiceMessage(int command)1029     synchronized void deferProfileServiceMessage(int command) {
1030         Message msg = new Message();
1031         msg.what = command;
1032         deferMessage(msg);
1033     }
1034 
updateIncomingAllowedTimer()1035     private void updateIncomingAllowedTimer() {
1036         // Not doing a perfect exponential backoff because
1037         // we want two different rates. For all practical
1038         // purposes, this is good enough.
1039         if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
1040 
1041         mIncomingRejectTimer *= 5;
1042         if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
1043             mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
1044         }
1045         writeTimerValue(mIncomingRejectTimer);
1046     }
1047 
handleIncomingConnection(int command, boolean accept)1048     private boolean handleIncomingConnection(int command, boolean accept) {
1049         boolean ret = false;
1050         Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
1051         switch (command) {
1052             case CONNECT_HFP_INCOMING:
1053                 if (!accept) {
1054                     ret = mHeadsetService.rejectIncomingConnect(mDevice);
1055                     sendMessage(TRANSITION_TO_STABLE);
1056                     updateIncomingAllowedTimer();
1057                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
1058                     writeTimerValue(0);
1059                     ret =  mHeadsetService.acceptIncomingConnect(mDevice);
1060                 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
1061                     writeTimerValue(0);
1062                     handleConnectionOfOtherProfiles(command);
1063                     ret = mHeadsetService.createIncomingConnect(mDevice);
1064                 }
1065                 break;
1066             case CONNECT_A2DP_INCOMING:
1067                 if (!accept) {
1068                     ret = mA2dpService.allowIncomingConnect(mDevice, false);
1069                     sendMessage(TRANSITION_TO_STABLE);
1070                     updateIncomingAllowedTimer();
1071                 } else {
1072                     writeTimerValue(0);
1073                     ret = mA2dpService.allowIncomingConnect(mDevice, true);
1074                     handleConnectionOfOtherProfiles(command);
1075                 }
1076                 break;
1077             case CONNECT_HID_INCOMING:
1078                 if (!accept) {
1079                     ret = mService.allowIncomingProfileConnect(mDevice, false);
1080                     sendMessage(TRANSITION_TO_STABLE);
1081                     updateIncomingAllowedTimer();
1082                 } else {
1083                     writeTimerValue(0);
1084                     ret = mService.allowIncomingProfileConnect(mDevice, true);
1085                 }
1086                 break;
1087             default:
1088                 Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
1089                 break;
1090        }
1091        return ret;
1092     }
1093 
sendConnectionAccessIntent()1094     private void sendConnectionAccessIntent() {
1095         mConnectionAccessReplyReceived = false;
1096 
1097         if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
1098 
1099         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
1100         intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
1101         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
1102                         BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
1103         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1104         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
1105     }
1106 
sendConnectionAccessRemovalIntent()1107     private void sendConnectionAccessRemovalIntent() {
1108         mWakeLock.release();
1109         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
1110         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1111         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
1112     }
1113 
getTrust()1114     private int getTrust() {
1115         String address = mDevice.getAddress();
1116         if (mIncomingConnections != null) return mIncomingConnections.first;
1117         return CONNECTION_ACCESS_UNDEFINED;
1118     }
1119 
1120 
getStringValue(long value)1121     private String getStringValue(long value) {
1122         StringBuilder sbr = new StringBuilder();
1123         sbr.append(Long.toString(System.currentTimeMillis()));
1124         sbr.append("-");
1125         sbr.append(Long.toString(value));
1126         return sbr.toString();
1127     }
1128 
setTrust(int value)1129     private void setTrust(int value) {
1130         String second;
1131         if (mIncomingConnections == null) {
1132             second = getStringValue(INIT_INCOMING_REJECT_TIMER);
1133         } else {
1134             second = mIncomingConnections.second;
1135         }
1136 
1137         mIncomingConnections = new Pair(value, second);
1138         mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
1139     }
1140 
writeTimerValue(long value)1141     private void writeTimerValue(long value) {
1142         Integer first;
1143         if (mIncomingConnections == null) {
1144             first = CONNECTION_ACCESS_UNDEFINED;
1145         } else {
1146             first = mIncomingConnections.first;
1147         }
1148         mIncomingConnections = new Pair(first, getStringValue(value));
1149         mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
1150     }
1151 
readTimerValue()1152     private long readTimerValue() {
1153         if (mIncomingConnections == null)
1154             return 0;
1155         String value = mIncomingConnections.second;
1156         String[] splits = value.split("-");
1157         if (splits != null && splits.length == 2) {
1158             return Long.parseLong(splits[1]);
1159         }
1160         return 0;
1161     }
1162 
readIncomingAllowedValue()1163     private boolean readIncomingAllowedValue() {
1164         if (readTimerValue() == 0) return true;
1165         String value = mIncomingConnections.second;
1166         String[] splits = value.split("-");
1167         if (splits != null && splits.length == 2) {
1168             long val1 = Long.parseLong(splits[0]);
1169             long val2 = Long.parseLong(splits[1]);
1170             if (val1 + val2 <= System.currentTimeMillis()) {
1171                 return true;
1172             }
1173         }
1174         return false;
1175     }
1176 
processCommand(int command)1177     synchronized boolean processCommand(int command) {
1178         log("Processing command:" + command);
1179         switch(command) {
1180             case  CONNECT_HFP_OUTGOING:
1181                 if (mHeadsetService == null) {
1182                     deferProfileServiceMessage(command);
1183                 } else {
1184                     return mHeadsetService.connectHeadsetInternal(mDevice);
1185                 }
1186                 break;
1187             case CONNECT_HFP_INCOMING:
1188                 if (mHeadsetService == null) {
1189                     deferProfileServiceMessage(command);
1190                 } else {
1191                     processIncomingConnectCommand(command);
1192                     return true;
1193                 }
1194                 break;
1195             case CONNECT_A2DP_OUTGOING:
1196                 if (mA2dpService != null) {
1197                     return mA2dpService.connectSinkInternal(mDevice);
1198                 }
1199                 break;
1200             case CONNECT_A2DP_INCOMING:
1201                 processIncomingConnectCommand(command);
1202                 return true;
1203             case CONNECT_HID_OUTGOING:
1204                 return mService.connectInputDeviceInternal(mDevice);
1205             case CONNECT_HID_INCOMING:
1206                 processIncomingConnectCommand(command);
1207                 return true;
1208             case DISCONNECT_HFP_OUTGOING:
1209                 if (mHeadsetService == null) {
1210                     deferProfileServiceMessage(command);
1211                 } else {
1212                     // Disconnect PBAP
1213                     // TODO(): Add PBAP to the state machine.
1214                     Message m = new Message();
1215                     m.what = DISCONNECT_PBAP_OUTGOING;
1216                     deferMessage(m);
1217                     if (mHeadsetService.getPriority(mDevice) ==
1218                         BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
1219                         mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
1220                     }
1221                     return mHeadsetService.disconnectHeadsetInternal(mDevice);
1222                 }
1223                 break;
1224             case DISCONNECT_HFP_INCOMING:
1225                 // ignore
1226                 return true;
1227             case DISCONNECT_A2DP_INCOMING:
1228                 // ignore
1229                 return true;
1230             case DISCONNECT_A2DP_OUTGOING:
1231                 if (mA2dpService != null) {
1232                     if (mA2dpService.getPriority(mDevice) ==
1233                         BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
1234                         mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
1235                     }
1236                     return mA2dpService.disconnectSinkInternal(mDevice);
1237                 }
1238                 break;
1239             case DISCONNECT_HID_INCOMING:
1240                 // ignore
1241                 return true;
1242             case DISCONNECT_HID_OUTGOING:
1243                 if (mService.getInputDevicePriority(mDevice) ==
1244                     BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
1245                     mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
1246                 }
1247                 return mService.disconnectInputDeviceInternal(mDevice);
1248             case DISCONNECT_PBAP_OUTGOING:
1249                 if (!mPbapServiceConnected) {
1250                     deferProfileServiceMessage(command);
1251                 } else {
1252                     return mPbapService.disconnect();
1253                 }
1254                 break;
1255             case UNPAIR:
1256                 writeTimerValue(INIT_INCOMING_REJECT_TIMER);
1257                 setTrust(CONNECTION_ACCESS_UNDEFINED);
1258                 return mService.removeBondInternal(mDevice.getAddress());
1259             default:
1260                 Log.e(TAG, "Error: Unknown Command");
1261         }
1262         return false;
1263     }
1264 
processIncomingConnectCommand(int command)1265     private void processIncomingConnectCommand(int command) {
1266         // Check if device is already trusted
1267         int access = getTrust();
1268         if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
1269             handleIncomingConnection(command, true);
1270         } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
1271                    !readIncomingAllowedValue()) {
1272             handleIncomingConnection(command, false);
1273         } else {
1274             sendConnectionAccessIntent();
1275             Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
1276             sendMessageDelayed(msg,
1277                                CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
1278         }
1279     }
1280 
handleConnectionOfOtherProfiles(int command)1281     private void handleConnectionOfOtherProfiles(int command) {
1282         // The white paper recommendations mentions that when there is a
1283         // link loss, it is the responsibility of the remote device to connect.
1284         // Many connect only 1 profile - and they connect the second profile on
1285         // some user action (like play being pressed) and so we need this code.
1286         // Auto Connect code only connects to the last connected device - which
1287         // is useful in cases like when the phone reboots. But consider the
1288         // following case:
1289         // User is connected to the car's phone and  A2DP profile.
1290         // User comes to the desk  and places the phone in the dock
1291         // (or any speaker or music system or even another headset) and thus
1292         // gets connected to the A2DP profile.  User goes back to the car.
1293         // Ideally the car's system is supposed to send incoming connections
1294         // from both Handsfree and A2DP profile. But they don't. The Auto
1295         // connect code, will not work here because we only auto connect to the
1296         // last connected device for that profile which in this case is the dock.
1297         // Now suppose a user is using 2 headsets simultaneously, one for the
1298         // phone profile one for the A2DP profile. If this is the use case, we
1299         // expect the user to use the preference to turn off the A2DP profile in
1300         // the Settings screen for the first headset. Else, after link loss,
1301         // there can be an incoming connection from the first headset which
1302         // might result in the connection of the A2DP profile (if the second
1303         // headset is slower) and thus the A2DP profile on the second headset
1304         // will never get connected.
1305         //
1306         // TODO(): Handle other profiles here.
1307         switch (command) {
1308             case CONNECT_HFP_INCOMING:
1309                 // Connect A2DP if there is no incoming connection
1310                 // If the priority is OFF - don't auto connect.
1311                 if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
1312                         mA2dpService.getPriority(mDevice) ==
1313                             BluetoothProfile.PRIORITY_AUTO_CONNECT) {
1314                     Message msg = new Message();
1315                     msg.what = CONNECT_OTHER_PROFILES;
1316                     msg.arg1 = CONNECT_A2DP_OUTGOING;
1317                     sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1318                 }
1319                 break;
1320             case CONNECT_A2DP_INCOMING:
1321                 // This is again against spec. HFP incoming connections should be made
1322                 // before A2DP, so we should not hit this case. But many devices
1323                 // don't follow this.
1324                 if (mHeadsetService != null &&
1325                     (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
1326                         mHeadsetService.getPriority(mDevice) ==
1327                             BluetoothProfile.PRIORITY_AUTO_CONNECT)) {
1328                     Message msg = new Message();
1329                     msg.what = CONNECT_OTHER_PROFILES;
1330                     msg.arg1 = CONNECT_HFP_OUTGOING;
1331                     sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1332                 }
1333                 break;
1334             default:
1335                 break;
1336         }
1337 
1338     }
1339 
getDevice()1340     /*package*/ BluetoothDevice getDevice() {
1341         return mDevice;
1342     }
1343 
log(String message)1344     private void log(String message) {
1345         if (DBG) {
1346             Log.i(TAG, "Device:" + mDevice + " Message:" + message);
1347         }
1348     }
1349 }
1350