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
17package android.bluetooth;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.Message;
24import android.os.PowerManager;
25import android.server.BluetoothA2dpService;
26import android.server.BluetoothService;
27import android.util.Log;
28import android.util.Pair;
29
30import com.android.internal.util.HierarchicalState;
31import com.android.internal.util.HierarchicalStateMachine;
32
33/**
34 * This class is the Profile connection state machine associated with a remote
35 * device. When the device bonds an instance of this class is created.
36 * This tracks incoming and outgoing connections of all the profiles. Incoming
37 * connections are preferred over outgoing connections and HFP preferred over
38 * A2DP. When the device is unbonded, the instance is removed.
39 *
40 * States:
41 * {@link BondedDevice}: This state represents a bonded device. When in this
42 * state none of the profiles are in transition states.
43 *
44 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
45 * state because of a outgoing Connect or Disconnect.
46 *
47 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
48 * state because of a incoming Connect or Disconnect.
49 *
50 * {@link IncomingA2dp}: A2dp profile connection is in a transition
51 * state because of a incoming Connect or Disconnect.
52 *
53 * {@link OutgoingA2dp}: A2dp profile connection is in a transition
54 * state because of a outgoing Connect or Disconnect.
55 *
56 * Todo(): Write tests for this class, when the Android Mock support is completed.
57 * @hide
58 */
59public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
60    private static final String TAG = "BluetoothDeviceProfileState";
61    private static final boolean DBG = false;
62
63    public static final int CONNECT_HFP_OUTGOING = 1;
64    public static final int CONNECT_HFP_INCOMING = 2;
65    public static final int CONNECT_A2DP_OUTGOING = 3;
66    public static final int CONNECT_A2DP_INCOMING = 4;
67
68    public static final int DISCONNECT_HFP_OUTGOING = 5;
69    private static final int DISCONNECT_HFP_INCOMING = 6;
70    public static final int DISCONNECT_A2DP_OUTGOING = 7;
71    public static final int DISCONNECT_A2DP_INCOMING = 8;
72    public static final int DISCONNECT_PBAP_OUTGOING = 9;
73
74    public static final int UNPAIR = 100;
75    public static final int AUTO_CONNECT_PROFILES = 101;
76    public static final int TRANSITION_TO_STABLE = 102;
77    public static final int CONNECT_OTHER_PROFILES = 103;
78    private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
79    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
80
81    private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
82    private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
83    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
84    private static final int CONNECTION_ACCESS_UNDEFINED = -1;
85    private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
86    private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
87
88    private static final String PREFS_NAME = "ConnectionAccess";
89
90    private BondedDevice mBondedDevice = new BondedDevice();
91    private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
92    private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
93    private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
94    private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
95
96    private Context mContext;
97    private BluetoothService mService;
98    private BluetoothA2dpService mA2dpService;
99    private BluetoothHeadset  mHeadsetService;
100    private BluetoothPbap     mPbapService;
101    private boolean mHeadsetServiceConnected;
102    private boolean mPbapServiceConnected;
103    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
104
105    private BluetoothDevice mDevice;
106    private int mHeadsetState;
107    private int mA2dpState;
108    private long mIncomingRejectTimer;
109    private boolean mConnectionAccessReplyReceived = false;
110    private Pair<Integer, String> mIncomingConnections;
111    private PowerManager.WakeLock mWakeLock;
112    private PowerManager mPowerManager;
113
114    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
115        @Override
116        public void onReceive(Context context, Intent intent) {
117            String action = intent.getAction();
118            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
119            if (!device.equals(mDevice)) return;
120
121            if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
122                int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
123                int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
124                int initiator = intent.getIntExtra(
125                    BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
126                    BluetoothHeadset.LOCAL_DISCONNECT);
127                // We trust this device now
128                if (newState == BluetoothHeadset.STATE_CONNECTED) {
129                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
130                }
131                mHeadsetState = newState;
132                if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
133                    initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
134                    sendMessage(DISCONNECT_HFP_INCOMING);
135                }
136                if (newState == BluetoothHeadset.STATE_CONNECTED ||
137                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
138                    sendMessage(TRANSITION_TO_STABLE);
139                }
140            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
141                int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
142                int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
143                mA2dpState = newState;
144                // We trust this device now
145                if (newState == BluetoothA2dp.STATE_CONNECTED) {
146                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
147                }
148                if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
149                           oldState == BluetoothA2dp.STATE_PLAYING) &&
150                           newState == BluetoothA2dp.STATE_DISCONNECTED) {
151                    sendMessage(DISCONNECT_A2DP_INCOMING);
152                }
153                if (newState == BluetoothA2dp.STATE_CONNECTED ||
154                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
155                    sendMessage(TRANSITION_TO_STABLE);
156                }
157            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
158                // This is technically not needed, but we can get stuck sometimes.
159                // For example, if incoming A2DP fails, we are not informed by Bluez
160                sendMessage(TRANSITION_TO_STABLE);
161            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
162                mWakeLock.release();
163                int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
164                                             BluetoothDevice.CONNECTION_ACCESS_NO);
165                Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
166                msg.arg1 = val;
167                sendMessage(msg);
168            }
169      }
170    };
171
172    private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
173      // This works only because these broadcast intents are "sticky"
174      Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
175      if (i != null) {
176          int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
177          if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
178              BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
179              if (device != null && autoConnectDevice.equals(device)) {
180                  return true;
181              }
182          }
183      }
184      return false;
185  }
186
187    public BluetoothDeviceProfileState(Context context, String address,
188          BluetoothService service, BluetoothA2dpService a2dpService) {
189        super(address);
190        mContext = context;
191        mDevice = new BluetoothDevice(address);
192        mService = service;
193        mA2dpService = a2dpService;
194
195        addState(mBondedDevice);
196        addState(mOutgoingHandsfree);
197        addState(mIncomingHandsfree);
198        addState(mIncomingA2dp);
199        addState(mOutgoingA2dp);
200        setInitialState(mBondedDevice);
201
202        IntentFilter filter = new IntentFilter();
203        // Fine-grained state broadcasts
204        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
205        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
206        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
207        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
208        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
209
210        mContext.registerReceiver(mBroadcastReceiver, filter);
211
212        HeadsetServiceListener l = new HeadsetServiceListener();
213        PbapServiceListener p = new PbapServiceListener();
214
215        mIncomingConnections = mService.getIncomingState(address);
216        mIncomingRejectTimer = readTimerValue();
217        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
218        mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
219                                              PowerManager.ACQUIRE_CAUSES_WAKEUP |
220                                              PowerManager.ON_AFTER_RELEASE, TAG);
221        mWakeLock.setReferenceCounted(false);
222    }
223
224    private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
225        public HeadsetServiceListener() {
226            mHeadsetService = new BluetoothHeadset(mContext, this);
227        }
228        public void onServiceConnected() {
229            synchronized(BluetoothDeviceProfileState.this) {
230                mHeadsetServiceConnected = true;
231            }
232        }
233        public void onServiceDisconnected() {
234            synchronized(BluetoothDeviceProfileState.this) {
235                mHeadsetServiceConnected = false;
236            }
237        }
238    }
239
240    private class PbapServiceListener implements BluetoothPbap.ServiceListener {
241        public PbapServiceListener() {
242            mPbapService = new BluetoothPbap(mContext, this);
243        }
244        public void onServiceConnected() {
245            synchronized(BluetoothDeviceProfileState.this) {
246                mPbapServiceConnected = true;
247            }
248        }
249        public void onServiceDisconnected() {
250            synchronized(BluetoothDeviceProfileState.this) {
251                mPbapServiceConnected = false;
252            }
253        }
254    }
255
256    private class BondedDevice extends HierarchicalState {
257        @Override
258        protected void enter() {
259            Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
260            Message m = new Message();
261            m.copyFrom(getCurrentMessage());
262            sendMessageAtFrontOfQueue(m);
263        }
264        @Override
265        protected boolean processMessage(Message message) {
266            log("ACL Connected State -> Processing Message: " + message.what);
267            switch(message.what) {
268                case CONNECT_HFP_OUTGOING:
269                case DISCONNECT_HFP_OUTGOING:
270                    transitionTo(mOutgoingHandsfree);
271                    break;
272                case CONNECT_HFP_INCOMING:
273                    transitionTo(mIncomingHandsfree);
274                    break;
275                case DISCONNECT_HFP_INCOMING:
276                    transitionTo(mIncomingHandsfree);
277                    break;
278                case CONNECT_A2DP_OUTGOING:
279                case DISCONNECT_A2DP_OUTGOING:
280                    transitionTo(mOutgoingA2dp);
281                    break;
282                case CONNECT_A2DP_INCOMING:
283                case DISCONNECT_A2DP_INCOMING:
284                    transitionTo(mIncomingA2dp);
285                    break;
286                case DISCONNECT_PBAP_OUTGOING:
287                    processCommand(DISCONNECT_PBAP_OUTGOING);
288                    break;
289                case UNPAIR:
290                    if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
291                        sendMessage(DISCONNECT_HFP_OUTGOING);
292                        deferMessage(message);
293                        break;
294                    } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
295                        sendMessage(DISCONNECT_A2DP_OUTGOING);
296                        deferMessage(message);
297                        break;
298                    }
299                    processCommand(UNPAIR);
300                    break;
301                case AUTO_CONNECT_PROFILES:
302                    if (isPhoneDocked(mDevice)) {
303                        // Don't auto connect to docks.
304                        break;
305                    } else if (!mHeadsetServiceConnected) {
306                        deferMessage(message);
307                    } else {
308                        if (mHeadsetService.getPriority(mDevice) ==
309                              BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
310                              !mHeadsetService.isConnected(mDevice)) {
311                            Log.i(TAG, "Headset:Auto Connect Profiles");
312                            mHeadsetService.connectHeadset(mDevice);
313                        }
314                        if (mA2dpService != null &&
315                              mA2dpService.getSinkPriority(mDevice) ==
316                              BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
317                              mA2dpService.getConnectedSinks().length == 0) {
318                            Log.i(TAG, "A2dp:Auto Connect Profiles");
319                            mA2dpService.connectSink(mDevice);
320                        }
321                    }
322                    break;
323                case CONNECT_OTHER_PROFILES:
324                    if (isPhoneDocked(mDevice)) {
325                       break;
326                    }
327                    if (message.arg1 == CONNECT_A2DP_OUTGOING) {
328                        if (mA2dpService != null &&
329                            mA2dpService.getConnectedSinks().length == 0) {
330                            Log.i(TAG, "A2dp:Connect Other Profiles");
331                            mA2dpService.connectSink(mDevice);
332                        }
333                    } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
334                        if (!mHeadsetServiceConnected) {
335                            deferMessage(message);
336                        } else {
337                            if (!mHeadsetService.isConnected(mDevice)) {
338                                Log.i(TAG, "Headset:Connect Other Profiles");
339                                mHeadsetService.connectHeadset(mDevice);
340                            }
341                        }
342                    }
343                    break;
344                case TRANSITION_TO_STABLE:
345                    // ignore.
346                    break;
347                default:
348                    return NOT_HANDLED;
349            }
350            return HANDLED;
351        }
352    }
353
354    private class OutgoingHandsfree extends HierarchicalState {
355        private boolean mStatus = false;
356        private int mCommand;
357
358        @Override
359        protected void enter() {
360            Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
361            mCommand = getCurrentMessage().what;
362            if (mCommand != CONNECT_HFP_OUTGOING &&
363                mCommand != DISCONNECT_HFP_OUTGOING) {
364                Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
365            }
366            mStatus = processCommand(mCommand);
367            if (!mStatus) {
368                sendMessage(TRANSITION_TO_STABLE);
369                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
370                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
371            }
372        }
373
374        @Override
375        protected boolean processMessage(Message message) {
376            log("OutgoingHandsfree State -> Processing Message: " + message.what);
377            Message deferMsg = new Message();
378            int command = message.what;
379            switch(command) {
380                case CONNECT_HFP_OUTGOING:
381                    if (command != mCommand) {
382                        // Disconnect followed by a connect - defer
383                        deferMessage(message);
384                    }
385                    break;
386                case CONNECT_HFP_INCOMING:
387                    if (mCommand == CONNECT_HFP_OUTGOING) {
388                        // Cancel outgoing connect, accept incoming
389                        cancelCommand(CONNECT_HFP_OUTGOING);
390                        transitionTo(mIncomingHandsfree);
391                    } else {
392                        // We have done the disconnect but we are not
393                        // sure which state we are in at this point.
394                        deferMessage(message);
395                    }
396                    break;
397                case CONNECT_A2DP_INCOMING:
398                    // accept incoming A2DP, retry HFP_OUTGOING
399                    transitionTo(mIncomingA2dp);
400
401                    if (mStatus) {
402                        deferMsg.what = mCommand;
403                        deferMessage(deferMsg);
404                    }
405                    break;
406                case CONNECT_A2DP_OUTGOING:
407                    deferMessage(message);
408                    break;
409                case DISCONNECT_HFP_OUTGOING:
410                    if (mCommand == CONNECT_HFP_OUTGOING) {
411                        // Cancel outgoing connect
412                        cancelCommand(CONNECT_HFP_OUTGOING);
413                        processCommand(DISCONNECT_HFP_OUTGOING);
414                    }
415                    // else ignore
416                    break;
417                case DISCONNECT_HFP_INCOMING:
418                    // When this happens the socket would be closed and the headset
419                    // state moved to DISCONNECTED, cancel the outgoing thread.
420                    // if it still is in CONNECTING state
421                    cancelCommand(CONNECT_HFP_OUTGOING);
422                    break;
423                case DISCONNECT_A2DP_OUTGOING:
424                    deferMessage(message);
425                    break;
426                case DISCONNECT_A2DP_INCOMING:
427                    // Bluez will handle the disconnect. If because of this the outgoing
428                    // handsfree connection has failed, then retry.
429                    if (mStatus) {
430                       deferMsg.what = mCommand;
431                       deferMessage(deferMsg);
432                    }
433                    break;
434                case DISCONNECT_PBAP_OUTGOING:
435                case UNPAIR:
436                case AUTO_CONNECT_PROFILES:
437                case CONNECT_OTHER_PROFILES:
438                    deferMessage(message);
439                    break;
440                case TRANSITION_TO_STABLE:
441                    transitionTo(mBondedDevice);
442                    break;
443                default:
444                    return NOT_HANDLED;
445            }
446            return HANDLED;
447        }
448    }
449
450    private class IncomingHandsfree extends HierarchicalState {
451        private boolean mStatus = false;
452        private int mCommand;
453
454        @Override
455        protected void enter() {
456            Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
457            mCommand = getCurrentMessage().what;
458            if (mCommand != CONNECT_HFP_INCOMING &&
459                mCommand != DISCONNECT_HFP_INCOMING) {
460                Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
461            }
462            mStatus = processCommand(mCommand);
463            if (!mStatus) {
464                sendMessage(TRANSITION_TO_STABLE);
465                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
466                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
467            }
468        }
469
470        @Override
471        protected boolean processMessage(Message message) {
472            log("IncomingHandsfree State -> Processing Message: " + message.what);
473            switch(message.what) {
474                case CONNECT_HFP_OUTGOING:
475                    deferMessage(message);
476                    break;
477                case CONNECT_HFP_INCOMING:
478                    // Ignore
479                    Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
480                    break;
481                case CONNECTION_ACCESS_REQUEST_REPLY:
482                    int val = message.arg1;
483                    mConnectionAccessReplyReceived = true;
484                    boolean value = false;
485                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
486                        value = true;
487                    }
488                    setTrust(val);
489
490                    handleIncomingConnection(CONNECT_HFP_INCOMING, value);
491                    break;
492                case CONNECTION_ACCESS_REQUEST_EXPIRY:
493                    if (!mConnectionAccessReplyReceived) {
494                        handleIncomingConnection(CONNECT_HFP_INCOMING, false);
495                        sendConnectionAccessRemovalIntent();
496                        sendMessage(TRANSITION_TO_STABLE);
497                    }
498                    break;
499                case CONNECT_A2DP_INCOMING:
500                    // Serialize the commands.
501                    deferMessage(message);
502                    break;
503                case CONNECT_A2DP_OUTGOING:
504                    deferMessage(message);
505                    break;
506                case DISCONNECT_HFP_OUTGOING:
507                    // We don't know at what state we are in the incoming HFP connection state.
508                    // We can be changing from DISCONNECTED to CONNECTING, or
509                    // from CONNECTING to CONNECTED, so serializing this command is
510                    // the safest option.
511                    deferMessage(message);
512                    break;
513                case DISCONNECT_HFP_INCOMING:
514                    // Nothing to do here, we will already be DISCONNECTED
515                    // by this point.
516                    break;
517                case DISCONNECT_A2DP_OUTGOING:
518                    deferMessage(message);
519                    break;
520                case DISCONNECT_A2DP_INCOMING:
521                    // Bluez handles incoming A2DP disconnect.
522                    // If this causes incoming HFP to fail, it is more of a headset problem
523                    // since both connections are incoming ones.
524                    break;
525                case DISCONNECT_PBAP_OUTGOING:
526                case UNPAIR:
527                case AUTO_CONNECT_PROFILES:
528                case CONNECT_OTHER_PROFILES:
529                    deferMessage(message);
530                    break;
531                case TRANSITION_TO_STABLE:
532                    transitionTo(mBondedDevice);
533                    break;
534                default:
535                    return NOT_HANDLED;
536            }
537            return HANDLED;
538        }
539    }
540
541    private class OutgoingA2dp extends HierarchicalState {
542        private boolean mStatus = false;
543        private int mCommand;
544
545        @Override
546        protected void enter() {
547            Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
548            mCommand = getCurrentMessage().what;
549            if (mCommand != CONNECT_A2DP_OUTGOING &&
550                mCommand != DISCONNECT_A2DP_OUTGOING) {
551                Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
552            }
553            mStatus = processCommand(mCommand);
554            if (!mStatus) {
555                sendMessage(TRANSITION_TO_STABLE);
556                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
557                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
558            }
559        }
560
561        @Override
562        protected boolean processMessage(Message message) {
563            log("OutgoingA2dp State->Processing Message: " + message.what);
564            Message deferMsg = new Message();
565            switch(message.what) {
566                case CONNECT_HFP_OUTGOING:
567                    processCommand(CONNECT_HFP_OUTGOING);
568
569                    // Don't cancel A2DP outgoing as there is no guarantee it
570                    // will get canceled.
571                    // It might already be connected but we might not have got the
572                    // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
573                    // The worst case, the connection will fail, retry.
574                    // The same applies to Disconnecting an A2DP connection.
575                    if (mStatus) {
576                        deferMsg.what = mCommand;
577                        deferMessage(deferMsg);
578                    }
579                    break;
580                case CONNECT_HFP_INCOMING:
581                    processCommand(CONNECT_HFP_INCOMING);
582
583                    // Don't cancel A2DP outgoing as there is no guarantee
584                    // it will get canceled.
585                    // The worst case, the connection will fail, retry.
586                    if (mStatus) {
587                        deferMsg.what = mCommand;
588                        deferMessage(deferMsg);
589                    }
590                    break;
591                case CONNECT_A2DP_INCOMING:
592                    // Bluez will take care of conflicts between incoming and outgoing
593                    // connections.
594                    transitionTo(mIncomingA2dp);
595                    break;
596                case CONNECT_A2DP_OUTGOING:
597                    // Ignore
598                    break;
599                case DISCONNECT_HFP_OUTGOING:
600                    deferMessage(message);
601                    break;
602                case DISCONNECT_HFP_INCOMING:
603                    // At this point, we are already disconnected
604                    // with HFP. Sometimes A2DP connection can
605                    // fail due to the disconnection of HFP. So add a retry
606                    // for the A2DP.
607                    if (mStatus) {
608                        deferMsg.what = mCommand;
609                        deferMessage(deferMsg);
610                    }
611                    break;
612                case DISCONNECT_A2DP_OUTGOING:
613                    deferMessage(message);
614                    break;
615                case DISCONNECT_A2DP_INCOMING:
616                    // Ignore, will be handled by Bluez
617                    break;
618                case DISCONNECT_PBAP_OUTGOING:
619                case UNPAIR:
620                case AUTO_CONNECT_PROFILES:
621                case CONNECT_OTHER_PROFILES:
622                    deferMessage(message);
623                    break;
624                case TRANSITION_TO_STABLE:
625                    transitionTo(mBondedDevice);
626                    break;
627                default:
628                    return NOT_HANDLED;
629            }
630            return HANDLED;
631        }
632    }
633
634    private class IncomingA2dp extends HierarchicalState {
635        private boolean mStatus = false;
636        private int mCommand;
637
638        @Override
639        protected void enter() {
640            Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
641            mCommand = getCurrentMessage().what;
642            if (mCommand != CONNECT_A2DP_INCOMING &&
643                mCommand != DISCONNECT_A2DP_INCOMING) {
644                Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
645            }
646            mStatus = processCommand(mCommand);
647            if (!mStatus) {
648                sendMessage(TRANSITION_TO_STABLE);
649                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
650                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
651            }
652        }
653
654        @Override
655        protected boolean processMessage(Message message) {
656            log("IncomingA2dp State->Processing Message: " + message.what);
657            Message deferMsg = new Message();
658            switch(message.what) {
659                case CONNECT_HFP_OUTGOING:
660                    deferMessage(message);
661                    break;
662                case CONNECT_HFP_INCOMING:
663                    // Shouldn't happen, but serialize the commands.
664                    deferMessage(message);
665                    break;
666                case CONNECT_A2DP_INCOMING:
667                    // ignore
668                    break;
669                case CONNECTION_ACCESS_REQUEST_REPLY:
670                    int val = message.arg1;
671                    mConnectionAccessReplyReceived = true;
672                    boolean value = false;
673                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
674                        value = true;
675                    }
676                    setTrust(val);
677                    handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
678                    break;
679                case CONNECTION_ACCESS_REQUEST_EXPIRY:
680                    // The check protects the race condition between REQUEST_REPLY
681                    // and the timer expiry.
682                    if (!mConnectionAccessReplyReceived) {
683                        handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
684                        sendConnectionAccessRemovalIntent();
685                        sendMessage(TRANSITION_TO_STABLE);
686                    }
687                    break;
688                case CONNECT_A2DP_OUTGOING:
689                    // Defer message and retry
690                    deferMessage(message);
691                    break;
692                case DISCONNECT_HFP_OUTGOING:
693                    deferMessage(message);
694                    break;
695                case DISCONNECT_HFP_INCOMING:
696                    // Shouldn't happen but if does, we can handle it.
697                    // Depends if the headset can handle it.
698                    // Incoming A2DP will be handled by Bluez, Disconnect HFP
699                    // the socket would have already been closed.
700                    // ignore
701                    break;
702                case DISCONNECT_A2DP_OUTGOING:
703                    deferMessage(message);
704                    break;
705                case DISCONNECT_A2DP_INCOMING:
706                    // Ignore, will be handled by Bluez
707                    break;
708                case DISCONNECT_PBAP_OUTGOING:
709                case UNPAIR:
710                case AUTO_CONNECT_PROFILES:
711                case CONNECT_OTHER_PROFILES:
712                    deferMessage(message);
713                    break;
714                case TRANSITION_TO_STABLE:
715                    transitionTo(mBondedDevice);
716                    break;
717                default:
718                    return NOT_HANDLED;
719            }
720            return HANDLED;
721        }
722    }
723
724
725
726    synchronized void cancelCommand(int command) {
727        if (command == CONNECT_HFP_OUTGOING ) {
728            // Cancel the outgoing thread.
729            if (mHeadsetServiceConnected) {
730                mHeadsetService.cancelConnectThread();
731            }
732            // HeadsetService is down. Phone process most likely crashed.
733            // The thread would have got killed.
734        }
735    }
736
737    synchronized void deferProfileServiceMessage(int command) {
738        Message msg = new Message();
739        msg.what = command;
740        deferMessage(msg);
741    }
742
743    private void updateIncomingAllowedTimer() {
744        // Not doing a perfect exponential backoff because
745        // we want two different rates. For all practical
746        // purposes, this is good enough.
747        if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
748
749        mIncomingRejectTimer *= 5;
750        if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
751            mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
752        }
753        writeTimerValue(mIncomingRejectTimer);
754    }
755
756    private boolean handleIncomingConnection(int command, boolean accept) {
757        boolean ret = false;
758        Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
759        switch (command) {
760            case CONNECT_HFP_INCOMING:
761                if (!accept) {
762                    ret = mHeadsetService.rejectIncomingConnect(mDevice);
763                    sendMessage(TRANSITION_TO_STABLE);
764                    updateIncomingAllowedTimer();
765                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
766                    writeTimerValue(0);
767                    ret =  mHeadsetService.acceptIncomingConnect(mDevice);
768                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
769                    writeTimerValue(0);
770                    handleConnectionOfOtherProfiles(command);
771                    ret = mHeadsetService.createIncomingConnect(mDevice);
772                }
773                break;
774            case CONNECT_A2DP_INCOMING:
775                if (!accept) {
776                    ret = mA2dpService.allowIncomingConnect(mDevice, false);
777                    sendMessage(TRANSITION_TO_STABLE);
778                    updateIncomingAllowedTimer();
779                } else {
780                    writeTimerValue(0);
781                    ret = mA2dpService.allowIncomingConnect(mDevice, true);
782                    handleConnectionOfOtherProfiles(command);
783                }
784                break;
785            default:
786                Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
787                break;
788       }
789       return ret;
790    }
791
792    private void sendConnectionAccessIntent() {
793        mConnectionAccessReplyReceived = false;
794
795        if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
796
797        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
798        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
799        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
800    }
801
802    private void sendConnectionAccessRemovalIntent() {
803        mWakeLock.release();
804        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
805        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
806        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
807    }
808
809    private int getTrust() {
810        String address = mDevice.getAddress();
811        if (mIncomingConnections != null) return mIncomingConnections.first;
812        return CONNECTION_ACCESS_UNDEFINED;
813    }
814
815
816    private String getStringValue(long value) {
817        StringBuilder sbr = new StringBuilder();
818        sbr.append(Long.toString(System.currentTimeMillis()));
819        sbr.append("-");
820        sbr.append(Long.toString(value));
821        return sbr.toString();
822    }
823
824    private void setTrust(int value) {
825        String second;
826        if (mIncomingConnections == null) {
827            second = getStringValue(INIT_INCOMING_REJECT_TIMER);
828        } else {
829            second = mIncomingConnections.second;
830        }
831
832        mIncomingConnections = new Pair(value, second);
833        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
834    }
835
836    private void writeTimerValue(long value) {
837        Integer first;
838        if (mIncomingConnections == null) {
839            first = CONNECTION_ACCESS_UNDEFINED;
840        } else {
841            first = mIncomingConnections.first;
842        }
843        mIncomingConnections = new Pair(first, getStringValue(value));
844        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
845    }
846
847    private long readTimerValue() {
848        if (mIncomingConnections == null)
849            return 0;
850        String value = mIncomingConnections.second;
851        String[] splits = value.split("-");
852        if (splits != null && splits.length == 2) {
853            return Long.parseLong(splits[1]);
854        }
855        return 0;
856    }
857
858    private boolean readIncomingAllowedValue() {
859        if (readTimerValue() == 0) return true;
860        String value = mIncomingConnections.second;
861        String[] splits = value.split("-");
862        if (splits != null && splits.length == 2) {
863            long val1 = Long.parseLong(splits[0]);
864            long val2 = Long.parseLong(splits[1]);
865            if (val1 + val2 <= System.currentTimeMillis()) {
866                return true;
867            }
868        }
869        return false;
870    }
871
872    synchronized boolean processCommand(int command) {
873        Log.e(TAG, "Processing command:" + command);
874        Message msg;
875        switch(command) {
876            case  CONNECT_HFP_OUTGOING:
877                if (mHeadsetService != null) {
878                    return mHeadsetService.connectHeadsetInternal(mDevice);
879                }
880                break;
881            case CONNECT_HFP_INCOMING:
882                if (!mHeadsetServiceConnected) {
883                    deferProfileServiceMessage(command);
884                } else {
885                    // Check if device is already trusted
886                    int access = getTrust();
887                    if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
888                        handleIncomingConnection(command, true);
889                    } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
890                               !readIncomingAllowedValue()) {
891                        handleIncomingConnection(command, false);
892                    } else {
893                        sendConnectionAccessIntent();
894                        msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
895                        sendMessageDelayed(msg,
896                                CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
897                    }
898                    return true;
899                }
900                break;
901            case CONNECT_A2DP_OUTGOING:
902                if (mA2dpService != null) {
903                    return mA2dpService.connectSinkInternal(mDevice);
904                }
905                break;
906            case CONNECT_A2DP_INCOMING:
907                // Check if device is already trusted
908                int access = getTrust();
909                if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
910                    handleIncomingConnection(command, true);
911                } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
912                           !readIncomingAllowedValue()) {
913                    handleIncomingConnection(command, false);
914                } else {
915                    sendConnectionAccessIntent();
916                    msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
917                    sendMessageDelayed(msg,
918                            CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
919                }
920                return true;
921            case DISCONNECT_HFP_OUTGOING:
922                if (!mHeadsetServiceConnected) {
923                    deferProfileServiceMessage(command);
924                } else {
925                    // Disconnect PBAP
926                    // TODO(): Add PBAP to the state machine.
927                    Message m = new Message();
928                    m.what = DISCONNECT_PBAP_OUTGOING;
929                    deferMessage(m);
930                    if (mHeadsetService.getPriority(mDevice) ==
931                        BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
932                        mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
933                    }
934                    return mHeadsetService.disconnectHeadsetInternal(mDevice);
935                }
936                break;
937            case DISCONNECT_HFP_INCOMING:
938                // ignore
939                return true;
940            case DISCONNECT_A2DP_INCOMING:
941                // ignore
942                return true;
943            case DISCONNECT_A2DP_OUTGOING:
944                if (mA2dpService != null) {
945                    if (mA2dpService.getSinkPriority(mDevice) ==
946                        BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
947                        mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
948                    }
949                    return mA2dpService.disconnectSinkInternal(mDevice);
950                }
951                break;
952            case DISCONNECT_PBAP_OUTGOING:
953                if (!mPbapServiceConnected) {
954                    deferProfileServiceMessage(command);
955                } else {
956                    return mPbapService.disconnect();
957                }
958                break;
959            case UNPAIR:
960                writeTimerValue(INIT_INCOMING_REJECT_TIMER);
961                setTrust(CONNECTION_ACCESS_UNDEFINED);
962                return mService.removeBondInternal(mDevice.getAddress());
963            default:
964                Log.e(TAG, "Error: Unknown Command");
965        }
966        return false;
967    }
968
969    private void handleConnectionOfOtherProfiles(int command) {
970        // The white paper recommendations mentions that when there is a
971        // link loss, it is the responsibility of the remote device to connect.
972        // Many connect only 1 profile - and they connect the second profile on
973        // some user action (like play being pressed) and so we need this code.
974        // Auto Connect code only connects to the last connected device - which
975        // is useful in cases like when the phone reboots. But consider the
976        // following case:
977        // User is connected to the car's phone and  A2DP profile.
978        // User comes to the desk  and places the phone in the dock
979        // (or any speaker or music system or even another headset) and thus
980        // gets connected to the A2DP profile.  User goes back to the car.
981        // Ideally the car's system is supposed to send incoming connections
982        // from both Handsfree and A2DP profile. But they don't. The Auto
983        // connect code, will not work here because we only auto connect to the
984        // last connected device for that profile which in this case is the dock.
985        // Now suppose a user is using 2 headsets simultaneously, one for the
986        // phone profile one for the A2DP profile. If this is the use case, we
987        // expect the user to use the preference to turn off the A2DP profile in
988        // the Settings screen for the first headset. Else, after link loss,
989        // there can be an incoming connection from the first headset which
990        // might result in the connection of the A2DP profile (if the second
991        // headset is slower) and thus the A2DP profile on the second headset
992        // will never get connected.
993        //
994        // TODO(): Handle other profiles here.
995        switch (command) {
996            case CONNECT_HFP_INCOMING:
997                // Connect A2DP if there is no incoming connection
998                // If the priority is OFF - don't auto connect.
999                if (mA2dpService.getSinkPriority(mDevice) == BluetoothA2dp.PRIORITY_ON ||
1000                        mA2dpService.getSinkPriority(mDevice) ==
1001                            BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
1002                    Message msg = new Message();
1003                    msg.what = CONNECT_OTHER_PROFILES;
1004                    msg.arg1 = CONNECT_A2DP_OUTGOING;
1005                    sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1006                }
1007                break;
1008            case CONNECT_A2DP_INCOMING:
1009                // This is again against spec. HFP incoming connections should be made
1010                // before A2DP, so we should not hit this case. But many devices
1011                // don't follow this.
1012                if (mHeadsetService.getPriority(mDevice) == BluetoothHeadset.PRIORITY_ON
1013                        || mHeadsetService.getPriority(mDevice) ==
1014                            BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
1015                    Message msg = new Message();
1016                    msg.what = CONNECT_OTHER_PROFILES;
1017                    msg.arg1 = CONNECT_HFP_OUTGOING;
1018                    sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1019                }
1020                break;
1021            default:
1022                break;
1023        }
1024
1025    }
1026
1027    /*package*/ BluetoothDevice getDevice() {
1028        return mDevice;
1029    }
1030
1031    private void log(String message) {
1032        if (DBG) {
1033            Log.i(TAG, "Device:" + mDevice + " Message:" + message);
1034        }
1035    }
1036}
1037