HeadsetClientStateMachine.java revision b4db71d3822ccf252fe09a7490f532c3b4fadd0c
1/*
2 * Copyright (c) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Bluetooth Headset Client StateMachine
19 *                      (Disconnected)
20 *                           | ^  ^
21 *                   CONNECT | |  | DISCONNECTED
22 *                           V |  |
23 *                   (Connecting) |
24 *                           |    |
25 *                 CONNECTED |    | DISCONNECT
26 *                           V    |
27 *                        (Connected)
28 *                           |    ^
29 *             CONNECT_AUDIO |    | DISCONNECT_AUDIO
30 *                           V    |
31 *                         (AudioOn)
32 */
33
34package com.android.bluetooth.hfpclient;
35
36import android.bluetooth.BluetoothAdapter;
37import android.bluetooth.BluetoothDevice;
38import android.bluetooth.BluetoothHeadsetClient;
39import android.bluetooth.BluetoothHeadsetClientCall;
40import android.bluetooth.BluetoothProfile;
41import android.bluetooth.BluetoothUuid;
42import android.content.Context;
43import android.content.Intent;
44import android.media.AudioManager;
45import android.os.Bundle;
46import android.os.Looper;
47import android.os.Message;
48import android.os.ParcelUuid;
49import android.os.SystemClock;
50import android.support.annotation.VisibleForTesting;
51import android.util.Log;
52import android.util.Pair;
53
54import com.android.bluetooth.Utils;
55import com.android.bluetooth.btservice.AdapterService;
56import com.android.bluetooth.btservice.ProfileService;
57import com.android.internal.util.IState;
58import com.android.internal.util.State;
59import com.android.internal.util.StateMachine;
60
61import java.util.ArrayList;
62import java.util.Arrays;
63import java.util.HashSet;
64import java.util.Hashtable;
65import java.util.Iterator;
66import java.util.LinkedList;
67import java.util.List;
68import java.util.Queue;
69import java.util.Set;
70
71public class HeadsetClientStateMachine extends StateMachine {
72    private static final String TAG = "HeadsetClientStateMachine";
73    private static final boolean DBG = false;
74
75    static final int NO_ACTION = 0;
76
77    // external actions
78    public static final int AT_OK = 0;
79    public static final int CONNECT = 1;
80    public static final int DISCONNECT = 2;
81    public static final int CONNECT_AUDIO = 3;
82    public static final int DISCONNECT_AUDIO = 4;
83    public static final int VOICE_RECOGNITION_START = 5;
84    public static final int VOICE_RECOGNITION_STOP = 6;
85    public static final int SET_MIC_VOLUME = 7;
86    public static final int SET_SPEAKER_VOLUME = 8;
87    public static final int DIAL_NUMBER = 10;
88    public static final int ACCEPT_CALL = 12;
89    public static final int REJECT_CALL = 13;
90    public static final int HOLD_CALL = 14;
91    public static final int TERMINATE_CALL = 15;
92    public static final int ENTER_PRIVATE_MODE = 16;
93    public static final int SEND_DTMF = 17;
94    public static final int EXPLICIT_CALL_TRANSFER = 18;
95    public static final int DISABLE_NREC = 20;
96
97    // internal actions
98    private static final int QUERY_CURRENT_CALLS = 50;
99    private static final int QUERY_OPERATOR_NAME = 51;
100    private static final int SUBSCRIBER_INFO = 52;
101    private static final int CONNECTING_TIMEOUT = 53;
102
103    // special action to handle terminating specific call from multiparty call
104    static final int TERMINATE_SPECIFIC_CALL = 53;
105
106    // Timeouts.
107    @VisibleForTesting
108    static final int CONNECTING_TIMEOUT_MS = 10000;  // 10s
109    private static final int ROUTING_DELAY_MS = 250;
110
111    private static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
112    private static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
113
114    static final int HF_ORIGINATED_CALL_ID = -1;
115    private static final long OUTGOING_TIMEOUT_MILLI = 10 * 1000; // 10 seconds
116    private static final long QUERY_CURRENT_CALLS_WAIT_MILLIS = 2 * 1000; // 2 seconds
117
118    // Keep track of audio routing across all devices.
119    private static boolean sAudioIsRouted = true;
120
121    private final Disconnected mDisconnected;
122    private final Connecting mConnecting;
123    private final Connected mConnected;
124    private final AudioOn mAudioOn;
125    private State mPrevState;
126    private long mClccTimer = 0;
127
128    private final HeadsetClientService mService;
129
130    // Set of calls that represent the accurate state of calls that exists on AG and the calls that
131    // are currently in process of being notified to the AG from HF.
132    private final Hashtable<Integer, BluetoothHeadsetClientCall> mCalls = new Hashtable<>();
133    // Set of calls received from AG via the AT+CLCC command. We use this map to update the mCalls
134    // which is eventually used to inform the telephony stack of any changes to call on HF.
135    private final Hashtable<Integer, BluetoothHeadsetClientCall> mCallsUpdate = new Hashtable<>();
136
137    private int mIndicatorNetworkState;
138    private int mIndicatorNetworkType;
139    private int mIndicatorNetworkSignal;
140    private int mIndicatorBatteryLevel;
141
142    private String mOperatorName;
143    private String mSubscriberInfo;
144
145    private static int sMaxAmVcVol;
146    private static int sMinAmVcVol;
147
148    // queue of send actions (pair action, action_data)
149    private Queue<Pair<Integer, Object>> mQueuedActions;
150
151    // last executed command, before action is complete e.g. waiting for some
152    // indicator
153    private Pair<Integer, Object> mPendingAction;
154
155    private static AudioManager sAudioManager;
156    private int mAudioState;
157    private boolean mAudioWbs;
158    private int mVoiceRecognitionActive;
159    private final BluetoothAdapter mAdapter;
160
161    // currently connected device
162    private BluetoothDevice mCurrentDevice = null;
163
164    // general peer features and call handling features
165    private int mPeerFeatures;
166    private int mChldFeatures;
167
168    // Accessor for the states, useful for reusing the state machines
169    public IState getDisconnectedState() {
170        return mDisconnected;
171    }
172
173    public void dump(StringBuilder sb) {
174        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
175        ProfileService.println(sb, "mAudioState: " + mAudioState);
176        ProfileService.println(sb, "mAudioWbs: " + mAudioWbs);
177        ProfileService.println(sb, "mIndicatorNetworkState: " + mIndicatorNetworkState);
178        ProfileService.println(sb, "mIndicatorNetworkType: " + mIndicatorNetworkType);
179        ProfileService.println(sb, "mIndicatorNetworkSignal: " + mIndicatorNetworkSignal);
180        ProfileService.println(sb, "mIndicatorBatteryLevel: " + mIndicatorBatteryLevel);
181        ProfileService.println(sb, "mOperatorName: " + mOperatorName);
182        ProfileService.println(sb, "mSubscriberInfo: " + mSubscriberInfo);
183
184        ProfileService.println(sb, "mCalls:");
185        if (mCalls != null) {
186            for (BluetoothHeadsetClientCall call : mCalls.values()) {
187                ProfileService.println(sb, "  " + call);
188            }
189        }
190
191        ProfileService.println(sb, "mCallsUpdate:");
192        if (mCallsUpdate != null) {
193            for (BluetoothHeadsetClientCall call : mCallsUpdate.values()) {
194                ProfileService.println(sb, "  " + call);
195            }
196        }
197
198        ProfileService.println(sb, "State machine stats:");
199        ProfileService.println(sb, this.toString());
200    }
201
202    private void clearPendingAction() {
203        mPendingAction = new Pair<Integer, Object>(NO_ACTION, 0);
204    }
205
206    private void addQueuedAction(int action) {
207        addQueuedAction(action, 0);
208    }
209
210    private void addQueuedAction(int action, Object data) {
211        mQueuedActions.add(new Pair<Integer, Object>(action, data));
212    }
213
214    private void addQueuedAction(int action, int data) {
215        mQueuedActions.add(new Pair<Integer, Object>(action, data));
216    }
217
218    private BluetoothHeadsetClientCall getCall(int... states) {
219        if (DBG) {
220            Log.d(TAG, "getFromCallsWithStates states:" + Arrays.toString(states));
221        }
222        for (BluetoothHeadsetClientCall c : mCalls.values()) {
223            for (int s : states) {
224                if (c.getState() == s) {
225                    return c;
226                }
227            }
228        }
229        return null;
230    }
231
232    private int callsInState(int state) {
233        int i = 0;
234        for (BluetoothHeadsetClientCall c : mCalls.values()) {
235            if (c.getState() == state) {
236                i++;
237            }
238        }
239
240        return i;
241    }
242
243    private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {
244        if (DBG) {
245            Log.d(TAG, "sendCallChangedIntent " + c);
246        }
247        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
248        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
249        intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
250        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
251    }
252
253    private boolean queryCallsStart() {
254        if (DBG) {
255            Log.d(TAG, "queryCallsStart");
256        }
257        clearPendingAction();
258        NativeInterface.queryCurrentCallsNative(getByteAddress(mCurrentDevice));
259        addQueuedAction(QUERY_CURRENT_CALLS, 0);
260        return true;
261    }
262
263    private void queryCallsDone() {
264        if (DBG) {
265            Log.d(TAG, "queryCallsDone");
266        }
267        Iterator<Hashtable.Entry<Integer, BluetoothHeadsetClientCall>> it;
268
269        // mCalls has two types of calls:
270        // (a) Calls that are received from AG of a previous iteration of queryCallsStart()
271        // (b) Calls that are outgoing initiated from HF
272        // mCallsUpdate has all calls received from queryCallsUpdate() in current iteration of
273        // queryCallsStart().
274        //
275        // We use the following steps to make sure that calls are update correctly.
276        //
277        // If there are no calls initiated from HF (i.e. ID = -1) then:
278        // 1. All IDs which are common in mCalls & mCallsUpdate are updated and the upper layers are
279        // informed of the change calls (if any changes).
280        // 2. All IDs that are in mCalls but *not* in mCallsUpdate will be removed from mCalls and
281        // the calls should be terminated
282        // 3. All IDs that are new in mCallsUpdated should be added as new calls to mCalls.
283        //
284        // If there is an outgoing HF call, it is important to associate that call with one of the
285        // mCallsUpdated calls hence,
286        // 1. If from the above procedure we get N extra calls (i.e. {3}):
287        // choose the first call as the one to associate with the HF call.
288
289        // Create set of IDs for added calls, removed calls and consitent calls.
290        // WARN!!! Java Map -> Set has association hence changes to Set are reflected in the Map
291        // itself (i.e. removing an element from Set removes it from the Map hence use copy).
292        Set<Integer> currCallIdSet = new HashSet<Integer>();
293        currCallIdSet.addAll(mCalls.keySet());
294        // Remove the entry for unassigned call.
295        currCallIdSet.remove(HF_ORIGINATED_CALL_ID);
296
297        Set<Integer> newCallIdSet = new HashSet<Integer>();
298        newCallIdSet.addAll(mCallsUpdate.keySet());
299
300        // Added.
301        Set<Integer> callAddedIds = new HashSet<Integer>();
302        callAddedIds.addAll(newCallIdSet);
303        callAddedIds.removeAll(currCallIdSet);
304
305        // Removed.
306        Set<Integer> callRemovedIds = new HashSet<Integer>();
307        callRemovedIds.addAll(currCallIdSet);
308        callRemovedIds.removeAll(newCallIdSet);
309
310        // Retained.
311        Set<Integer> callRetainedIds = new HashSet<Integer>();
312        callRetainedIds.addAll(currCallIdSet);
313        callRetainedIds.retainAll(newCallIdSet);
314
315        if (DBG) {
316            Log.d(TAG, "currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
317                    + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
318                    + " callRetainedIds " + callRetainedIds);
319        }
320
321        // First thing is to try to associate the outgoing HF with a valid call.
322        Integer hfOriginatedAssoc = -1;
323        if (mCalls.containsKey(HF_ORIGINATED_CALL_ID)) {
324            BluetoothHeadsetClientCall c = mCalls.get(HF_ORIGINATED_CALL_ID);
325            long cCreationElapsed = c.getCreationElapsedMilli();
326            if (callAddedIds.size() > 0) {
327                if (DBG) {
328                    Log.d(TAG, "Associating the first call with HF originated call");
329                }
330                hfOriginatedAssoc = (Integer) callAddedIds.toArray()[0];
331                mCalls.put(hfOriginatedAssoc, mCalls.get(HF_ORIGINATED_CALL_ID));
332                mCalls.remove(HF_ORIGINATED_CALL_ID);
333
334                // Adjust this call in above sets.
335                callAddedIds.remove(hfOriginatedAssoc);
336                callRetainedIds.add(hfOriginatedAssoc);
337            } else if (SystemClock.elapsedRealtime() - cCreationElapsed > OUTGOING_TIMEOUT_MILLI) {
338                Log.w(TAG, "Outgoing call did not see a response, clear the calls and send CHUP");
339                // We send a terminate because we are in a bad state and trying to
340                // recover.
341                terminateCall();
342
343                // Clean out the state for outgoing call.
344                for (Integer idx : mCalls.keySet()) {
345                    BluetoothHeadsetClientCall c1 = mCalls.get(idx);
346                    c1.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
347                    sendCallChangedIntent(c1);
348                }
349                mCalls.clear();
350
351                // We return here, if there's any update to the phone we should get a
352                // follow up by getting some call indicators and hence update the calls.
353                return;
354            }
355        }
356
357        if (DBG) {
358            Log.d(TAG, "ADJUST: currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
359                    + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
360                    + " callRetainedIds " + callRetainedIds);
361        }
362
363        // Terminate & remove the calls that are done.
364        for (Integer idx : callRemovedIds) {
365            BluetoothHeadsetClientCall c = mCalls.remove(idx);
366            c.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
367            sendCallChangedIntent(c);
368        }
369
370        // Add the new calls.
371        for (Integer idx : callAddedIds) {
372            BluetoothHeadsetClientCall c = mCallsUpdate.get(idx);
373            mCalls.put(idx, c);
374            sendCallChangedIntent(c);
375        }
376
377        // Update the existing calls.
378        for (Integer idx : callRetainedIds) {
379            BluetoothHeadsetClientCall cOrig = mCalls.get(idx);
380            BluetoothHeadsetClientCall cUpdate = mCallsUpdate.get(idx);
381
382            // Update the necessary fields.
383            cOrig.setNumber(cUpdate.getNumber());
384            cOrig.setState(cUpdate.getState());
385            cOrig.setMultiParty(cUpdate.isMultiParty());
386
387            // Send update with original object (UUID, idx).
388            sendCallChangedIntent(cOrig);
389        }
390
391        if (mCalls.size() > 0) {
392            sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
393        }
394
395        mCallsUpdate.clear();
396    }
397
398    private void queryCallsUpdate(int id, int state, String number, boolean multiParty,
399            boolean outgoing) {
400        if (DBG) {
401            Log.d(TAG, "queryCallsUpdate: " + id);
402        }
403        mCallsUpdate.put(id,
404                new BluetoothHeadsetClientCall(mCurrentDevice, id, state, number, multiParty,
405                        outgoing));
406    }
407
408    private void acceptCall(int flag) {
409        int action = -1;
410
411        if (DBG) {
412            Log.d(TAG, "acceptCall: (" + flag + ")");
413        }
414
415        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
416                BluetoothHeadsetClientCall.CALL_STATE_WAITING);
417        if (c == null) {
418            c = getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
419                    BluetoothHeadsetClientCall.CALL_STATE_HELD);
420
421            if (c == null) {
422                return;
423            }
424        }
425
426        if (DBG) {
427            Log.d(TAG, "Call to accept: " + c);
428        }
429        switch (c.getState()) {
430            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
431                if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
432                    return;
433                }
434                action = HeadsetClientHalConstants.CALL_ACTION_ATA;
435                break;
436            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
437                if (callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) == 0) {
438                    // if no active calls present only plain accept is allowed
439                    if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
440                        return;
441                    }
442                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
443                    break;
444                }
445
446                // if active calls are present then we have the option to either terminate the
447                // existing call or hold the existing call. We hold the other call by default.
448                if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD
449                        || flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
450                    if (DBG) {
451                        Log.d(TAG, "Accepting call with accept and hold");
452                    }
453                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
454                } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE) {
455                    if (DBG) {
456                        Log.d(TAG, "Accepting call with accept and reject");
457                    }
458                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
459                } else {
460                    Log.e(TAG, "Aceept call with invalid flag: " + flag);
461                    return;
462                }
463                break;
464            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
465                if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD) {
466                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
467                } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE) {
468                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
469                } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) {
470                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3;
471                } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
472                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
473                } else {
474                    action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
475                }
476                break;
477            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
478                action = HeadsetClientHalConstants.CALL_ACTION_BTRH_1;
479                break;
480            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
481            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
482            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
483            default:
484                return;
485        }
486
487        if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD) {
488            // When unholding a call over Bluetooth make sure to route audio.
489            routeHfpAudio(true);
490        }
491
492        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
493            addQueuedAction(ACCEPT_CALL, action);
494        } else {
495            Log.e(TAG, "ERROR: Couldn't accept a call, action:" + action);
496        }
497    }
498
499    private void rejectCall() {
500        int action;
501
502        if (DBG) {
503            Log.d(TAG, "rejectCall");
504        }
505
506        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
507                BluetoothHeadsetClientCall.CALL_STATE_WAITING,
508                BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
509                BluetoothHeadsetClientCall.CALL_STATE_HELD);
510        if (c == null) {
511            if (DBG) {
512                Log.d(TAG, "No call to reject, returning.");
513            }
514            return;
515        }
516
517        switch (c.getState()) {
518            case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
519                action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
520                break;
521            case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
522            case BluetoothHeadsetClientCall.CALL_STATE_HELD:
523                action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
524                break;
525            case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
526                action = HeadsetClientHalConstants.CALL_ACTION_BTRH_2;
527                break;
528            case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
529            case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
530            case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
531            default:
532                return;
533        }
534
535        if (DBG) {
536            Log.d(TAG, "Reject call action " + action);
537        }
538        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
539            addQueuedAction(REJECT_CALL, action);
540        } else {
541            Log.e(TAG, "ERROR: Couldn't reject a call, action:" + action);
542        }
543    }
544
545    private void holdCall() {
546        int action;
547
548        if (DBG) {
549            Log.d(TAG, "holdCall");
550        }
551
552        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
553        if (c != null) {
554            action = HeadsetClientHalConstants.CALL_ACTION_BTRH_0;
555        } else {
556            c = getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
557            if (c == null) {
558                return;
559            }
560
561            action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
562        }
563
564        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
565            addQueuedAction(HOLD_CALL, action);
566        } else {
567            Log.e(TAG, "ERROR: Couldn't hold a call, action:" + action);
568        }
569    }
570
571    private void terminateCall() {
572        if (DBG) {
573            Log.d(TAG, "terminateCall");
574        }
575
576        int action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
577
578        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING,
579                BluetoothHeadsetClientCall.CALL_STATE_ALERTING,
580                BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
581        if (c == null) {
582            // If the call being terminated is currently held, switch the action to CHLD_0
583            c = getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD);
584            action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
585        }
586        if (c != null) {
587            if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
588                addQueuedAction(TERMINATE_CALL, action);
589            } else {
590                Log.e(TAG, "ERROR: Couldn't terminate outgoing call");
591            }
592        }
593    }
594
595    private void enterPrivateMode(int idx) {
596        if (DBG) {
597            Log.d(TAG, "enterPrivateMode: " + idx);
598        }
599
600        BluetoothHeadsetClientCall c = mCalls.get(idx);
601
602        if (c == null || c.getState() != BluetoothHeadsetClientCall.CALL_STATE_ACTIVE
603                || !c.isMultiParty()) {
604            return;
605        }
606
607        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
608                HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, idx)) {
609            addQueuedAction(ENTER_PRIVATE_MODE, c);
610        } else {
611            Log.e(TAG, "ERROR: Couldn't enter private " + " id:" + idx);
612        }
613    }
614
615    private void explicitCallTransfer() {
616        if (DBG) {
617            Log.d(TAG, "explicitCallTransfer");
618        }
619
620        // can't transfer call if there is not enough call parties
621        if (mCalls.size() < 2) {
622            return;
623        }
624
625        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
626                HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
627            addQueuedAction(EXPLICIT_CALL_TRANSFER);
628        } else {
629            Log.e(TAG, "ERROR: Couldn't transfer call");
630        }
631    }
632
633    public Bundle getCurrentAgFeatures() {
634        Bundle b = new Bundle();
635        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)
636                == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
637            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
638        }
639        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
640                == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
641            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
642        }
643        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)
644                == HeadsetClientHalConstants.PEER_FEAT_ECC) {
645            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
646        }
647
648        // add individual CHLD support extras
649        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)
650                == HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
651            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, true);
652        }
653        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)
654                == HeadsetClientHalConstants.CHLD_FEAT_REL) {
655            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL,
656                    true);
657        }
658        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)
659                == HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
660            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
661        }
662        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)
663                == HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
664            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
665        }
666        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)
667                == HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
668            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
669        }
670
671        return b;
672    }
673
674    HeadsetClientStateMachine(HeadsetClientService context, Looper looper) {
675        super(TAG, looper);
676        mService = context;
677
678        mAdapter = BluetoothAdapter.getDefaultAdapter();
679        if (sAudioManager == null) {
680            sAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
681            // Initialize hfp_enable into a known state.
682            routeHfpAudio(false);
683        }
684        mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
685        mAudioWbs = false;
686        mVoiceRecognitionActive = HeadsetClientHalConstants.VR_STATE_STOPPED;
687
688        mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
689        mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
690        mIndicatorNetworkSignal = 0;
691        mIndicatorBatteryLevel = 0;
692
693        sMaxAmVcVol = sAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
694        sMinAmVcVol = sAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);
695
696        mOperatorName = null;
697        mSubscriberInfo = null;
698
699        mQueuedActions = new LinkedList<Pair<Integer, Object>>();
700        clearPendingAction();
701
702        mCalls.clear();
703        mCallsUpdate.clear();
704
705        mDisconnected = new Disconnected();
706        mConnecting = new Connecting();
707        mConnected = new Connected();
708        mAudioOn = new AudioOn();
709
710        addState(mDisconnected);
711        addState(mConnecting);
712        addState(mConnected);
713        addState(mAudioOn, mConnected);
714
715        setInitialState(mDisconnected);
716    }
717
718    static HeadsetClientStateMachine make(HeadsetClientService context, Looper l) {
719        if (DBG) {
720            Log.d(TAG, "make");
721        }
722        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, l);
723        hfcsm.start();
724        return hfcsm;
725    }
726
727    static synchronized void routeHfpAudio(boolean enable) {
728        if (DBG) {
729            Log.d(TAG, "hfp_enable=" + enable);
730        }
731        if (enable && !sAudioIsRouted) {
732            sAudioManager.setParameters("hfp_enable=true");
733        } else if (!enable) {
734            sAudioManager.setParameters("hfp_enable=false");
735        }
736        sAudioIsRouted = enable;
737    }
738
739    public void doQuit() {
740        Log.d(TAG, "doQuit");
741        if (sAudioManager != null) {
742            routeHfpAudio(false);
743        }
744        quitNow();
745    }
746
747    public static void cleanup() {
748    }
749
750    static int hfToAmVol(int hfVol) {
751        int amRange = sMaxAmVcVol - sMinAmVcVol;
752        int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
753        int amOffset = (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange;
754        int amVol = sMinAmVcVol + amOffset;
755        Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
756        return amVol;
757    }
758
759    static int amToHfVol(int amVol) {
760        int amRange = (sMaxAmVcVol > sMinAmVcVol) ? (sMaxAmVcVol - sMinAmVcVol) : 1;
761        int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
762        int hfOffset = (hfRange * (amVol - sMinAmVcVol)) / amRange;
763        int hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
764        Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
765        return hfVol;
766    }
767
768    class Disconnected extends State {
769        @Override
770        public void enter() {
771            Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
772
773            // cleanup
774            mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
775            mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
776            mIndicatorNetworkSignal = 0;
777            mIndicatorBatteryLevel = 0;
778
779            mAudioWbs = false;
780
781            // will be set on connect
782
783            mOperatorName = null;
784            mSubscriberInfo = null;
785
786            mQueuedActions = new LinkedList<Pair<Integer, Object>>();
787            clearPendingAction();
788
789
790            mCurrentDevice = null;
791
792            mCalls.clear();
793            mCallsUpdate.clear();
794
795            mPeerFeatures = 0;
796            mChldFeatures = 0;
797
798            removeMessages(QUERY_CURRENT_CALLS);
799
800            if (mPrevState == mConnecting) {
801                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
802                        BluetoothProfile.STATE_CONNECTING);
803            } else if (mPrevState == mConnected || mPrevState == mAudioOn) {
804                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
805                        BluetoothProfile.STATE_CONNECTED);
806            } else if (mPrevState != null) { // null is the default state before Disconnected
807                Log.e(TAG, "Connected: Illegal state transition from " + mPrevState.getName()
808                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
809            }
810        }
811
812        @Override
813        public synchronized boolean processMessage(Message message) {
814            Log.d(TAG, "Disconnected process message: " + message.what);
815
816            if (mCurrentDevice != null) {
817                Log.e(TAG, "ERROR: current device not null in Disconnected");
818                return NOT_HANDLED;
819            }
820
821            switch (message.what) {
822                case CONNECT:
823                    BluetoothDevice device = (BluetoothDevice) message.obj;
824                    if (!NativeInterface.connectNative(getByteAddress(device))) {
825                        // No state transition is involved, fire broadcast immediately
826                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
827                                BluetoothProfile.STATE_DISCONNECTED);
828                        break;
829                    }
830                    mCurrentDevice = device;
831                    transitionTo(mConnecting);
832                    break;
833                case DISCONNECT:
834                    // ignore
835                    break;
836                case StackEvent.STACK_EVENT:
837                    StackEvent event = (StackEvent) message.obj;
838                    if (DBG) {
839                        Log.d(TAG, "Stack event type: " + event.type);
840                    }
841                    switch (event.type) {
842                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
843                            if (DBG) {
844                                Log.d(TAG, "Disconnected: Connection " + event.device
845                                        + " state changed:" + event.valueInt);
846                            }
847                            processConnectionEvent(event.valueInt, event.device);
848                            break;
849                        default:
850                            Log.e(TAG, "Disconnected: Unexpected stack event: " + event.type);
851                            break;
852                    }
853                    break;
854                default:
855                    return NOT_HANDLED;
856            }
857            return HANDLED;
858        }
859
860        // in Disconnected state
861        private void processConnectionEvent(int state, BluetoothDevice device) {
862            switch (state) {
863                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
864                    Log.w(TAG, "HFPClient Connecting from Disconnected state");
865                    if (okToConnect(device)) {
866                        Log.i(TAG, "Incoming AG accepted");
867                        mCurrentDevice = device;
868                        transitionTo(mConnecting);
869                    } else {
870                        Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
871                                + " bondState=" + device.getBondState());
872                        // reject the connection and stay in Disconnected state
873                        // itself
874                        NativeInterface.disconnectNative(getByteAddress(device));
875                        // the other profile connection should be initiated
876                        AdapterService adapterService = AdapterService.getAdapterService();
877                        // No state transition is involved, fire broadcast immediately
878                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
879                                BluetoothProfile.STATE_DISCONNECTED);
880                    }
881                    break;
882                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
883                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
884                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
885                default:
886                    Log.i(TAG, "ignoring state: " + state);
887                    break;
888            }
889        }
890
891        @Override
892        public void exit() {
893            if (DBG) {
894                Log.d(TAG, "Exit Disconnected: " + getCurrentMessage().what);
895            }
896            mPrevState = this;
897        }
898    }
899
900    class Connecting extends State {
901        @Override
902        public void enter() {
903            if (DBG) {
904                Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
905            }
906            // This message is either consumed in processMessage or
907            // removed in exit. It is safe to send a CONNECTING_TIMEOUT here since
908            // the only transition is when connection attempt is initiated.
909            sendMessageDelayed(CONNECTING_TIMEOUT, CONNECTING_TIMEOUT_MS);
910            if (mPrevState == mDisconnected) {
911                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTING,
912                        BluetoothProfile.STATE_DISCONNECTED);
913            } else {
914                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
915                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
916                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
917            }
918        }
919
920        @Override
921        public synchronized boolean processMessage(Message message) {
922            if (DBG) {
923                Log.d(TAG, "Connecting process message: " + message.what);
924            }
925
926            switch (message.what) {
927                case CONNECT:
928                case CONNECT_AUDIO:
929                case DISCONNECT:
930                    deferMessage(message);
931                    break;
932                case StackEvent.STACK_EVENT:
933                    StackEvent event = (StackEvent) message.obj;
934                    if (DBG) {
935                        Log.d(TAG, "Connecting: event type: " + event.type);
936                    }
937                    switch (event.type) {
938                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
939                            if (DBG) {
940                                Log.d(TAG,
941                                        "Connecting: Connection " + event.device + " state changed:"
942                                                + event.valueInt);
943                            }
944                            processConnectionEvent(event.valueInt, event.valueInt2, event.valueInt3,
945                                    event.device);
946                            break;
947                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
948                        case StackEvent.EVENT_TYPE_NETWORK_STATE:
949                        case StackEvent.EVENT_TYPE_ROAMING_STATE:
950                        case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
951                        case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
952                        case StackEvent.EVENT_TYPE_CALL:
953                        case StackEvent.EVENT_TYPE_CALLSETUP:
954                        case StackEvent.EVENT_TYPE_CALLHELD:
955                        case StackEvent.EVENT_TYPE_RESP_AND_HOLD:
956                        case StackEvent.EVENT_TYPE_CLIP:
957                        case StackEvent.EVENT_TYPE_CALL_WAITING:
958                        case StackEvent.EVENT_TYPE_VOLUME_CHANGED:
959                            deferMessage(message);
960                            break;
961                        case StackEvent.EVENT_TYPE_CMD_RESULT:
962                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
963                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
964                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
965                        default:
966                            Log.e(TAG, "Connecting: ignoring stack event: " + event.type);
967                            break;
968                    }
969                    break;
970                case CONNECTING_TIMEOUT:
971                    // We timed out trying to connect, transition to disconnected.
972                    Log.w(TAG, "Connection timeout for " + mCurrentDevice);
973                    transitionTo(mDisconnected);
974                    break;
975
976                default:
977                    Log.w(TAG, "Message not handled " + message);
978                    return NOT_HANDLED;
979            }
980            return HANDLED;
981        }
982
983        // in Connecting state
984        private void processConnectionEvent(int state, int peerFeat, int chldFeat,
985                BluetoothDevice device) {
986            switch (state) {
987                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
988                    transitionTo(mDisconnected);
989                    break;
990
991                case HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED:
992                    Log.d(TAG, "HFPClient Connected from Connecting state");
993
994                    mPeerFeatures = peerFeat;
995                    mChldFeatures = chldFeat;
996
997                    // We do not support devices which do not support enhanced call status (ECS).
998                    if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECS) == 0) {
999                        NativeInterface.disconnectNative(getByteAddress(device));
1000                        return;
1001                    }
1002
1003                    // Send AT+NREC to remote if supported by audio
1004                    if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED && (
1005                            (mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR)
1006                                    == HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
1007                        if (NativeInterface.sendATCmdNative(getByteAddress(mCurrentDevice),
1008                                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC, 1, 0,
1009                                null)) {
1010                            addQueuedAction(DISABLE_NREC);
1011                        } else {
1012                            Log.e(TAG, "Failed to send NREC");
1013                        }
1014                    }
1015
1016                    int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1017                    deferMessage(
1018                            obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME, amVol, 0));
1019                    // Mic is either in ON state (full volume) or OFF state. There is no way in
1020                    // Android to change the MIC volume.
1021                    deferMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
1022                            sAudioManager.isMicrophoneMute() ? 0 : 15, 0));
1023                    // query subscriber info
1024                    deferMessage(obtainMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO));
1025                    transitionTo(mConnected);
1026                    break;
1027
1028                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
1029                    if (!mCurrentDevice.equals(device)) {
1030                        Log.w(TAG, "incoming connection event, device: " + device);
1031                        // No state transition is involved, fire broadcast immediately
1032                        broadcastConnectionState(mCurrentDevice,
1033                                BluetoothProfile.STATE_DISCONNECTED,
1034                                BluetoothProfile.STATE_CONNECTING);
1035                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
1036                                BluetoothProfile.STATE_DISCONNECTED);
1037
1038                        mCurrentDevice = device;
1039                    }
1040                    break;
1041                case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
1042                    /* outgoing connecting started */
1043                    if (DBG) {
1044                        Log.d(TAG, "outgoing connection started, ignore");
1045                    }
1046                    break;
1047                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
1048                default:
1049                    Log.e(TAG, "Incorrect state: " + state);
1050                    break;
1051            }
1052        }
1053
1054        @Override
1055        public void exit() {
1056            if (DBG) {
1057                Log.d(TAG, "Exit Connecting: " + getCurrentMessage().what);
1058            }
1059            removeMessages(CONNECTING_TIMEOUT);
1060            mPrevState = this;
1061        }
1062    }
1063
1064    class Connected extends State {
1065        int mCommandedSpeakerVolume = -1;
1066
1067        @Override
1068        public void enter() {
1069            if (DBG) {
1070                Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
1071            }
1072            mAudioWbs = false;
1073            mCommandedSpeakerVolume = -1;
1074            if (mPrevState == mConnecting) {
1075                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
1076                        BluetoothProfile.STATE_CONNECTING);
1077            } else if (mPrevState != mAudioOn) {
1078                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
1079                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
1080                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
1081            }
1082        }
1083
1084        @Override
1085        public synchronized boolean processMessage(Message message) {
1086            if (DBG) {
1087                Log.d(TAG, "Connected process message: " + message.what);
1088            }
1089            if (DBG) {
1090                if (mCurrentDevice == null) {
1091                    Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
1092                    return NOT_HANDLED;
1093                }
1094            }
1095
1096            switch (message.what) {
1097                case CONNECT:
1098                    BluetoothDevice device = (BluetoothDevice) message.obj;
1099                    if (mCurrentDevice.equals(device)) {
1100                        // already connected to this device, do nothing
1101                        break;
1102                    }
1103                    NativeInterface.connectNative(getByteAddress(device));
1104                    break;
1105                case DISCONNECT:
1106                    BluetoothDevice dev = (BluetoothDevice) message.obj;
1107                    if (!mCurrentDevice.equals(dev)) {
1108                        break;
1109                    }
1110                    if (NativeInterface.disconnectNative(getByteAddress(dev))) {
1111                        // No state transition is involved, fire broadcast immediately
1112                        broadcastConnectionState(dev, BluetoothProfile.STATE_DISCONNECTING,
1113                                BluetoothProfile.STATE_CONNECTED);
1114                    } else {
1115                        Log.e(TAG, "disconnectNative failed for " + dev);
1116                    }
1117                    break;
1118
1119                case CONNECT_AUDIO:
1120                    if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) {
1121                        Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice);
1122                        // No state transition is involved, fire broadcast immediately
1123                        broadcastAudioState(mCurrentDevice,
1124                                BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1125                                BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
1126                    } else { // We have successfully sent a connect request!
1127                        mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
1128                    }
1129                    break;
1130
1131                case DISCONNECT_AUDIO:
1132                    if (!NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
1133                        Log.e(TAG, "ERROR: Couldn't disconnect Audio for device " + mCurrentDevice);
1134                    }
1135                    break;
1136
1137                case VOICE_RECOGNITION_START:
1138                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
1139                        if (NativeInterface.startVoiceRecognitionNative(
1140                                    getByteAddress(mCurrentDevice))) {
1141                            addQueuedAction(VOICE_RECOGNITION_START);
1142                        } else {
1143                            Log.e(TAG, "ERROR: Couldn't start voice recognition");
1144                        }
1145                    }
1146                    break;
1147
1148                case VOICE_RECOGNITION_STOP:
1149                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
1150                        if (NativeInterface.stopVoiceRecognitionNative(
1151                                    getByteAddress(mCurrentDevice))) {
1152                            addQueuedAction(VOICE_RECOGNITION_STOP);
1153                        } else {
1154                            Log.e(TAG, "ERROR: Couldn't stop voice recognition");
1155                        }
1156                    }
1157                    break;
1158
1159                // Called only for Mute/Un-mute - Mic volume change is not allowed.
1160                case SET_MIC_VOLUME:
1161                    break;
1162                case SET_SPEAKER_VOLUME:
1163                    // This message should always contain the volume in AudioManager max normalized.
1164                    int amVol = message.arg1;
1165                    int hfVol = amToHfVol(amVol);
1166                    if (amVol != mCommandedSpeakerVolume) {
1167                        Log.d(TAG, "Volume" + amVol + ":" + mCommandedSpeakerVolume);
1168                        // Volume was changed by a 3rd party
1169                        mCommandedSpeakerVolume = -1;
1170                        if (NativeInterface.setVolumeNative(getByteAddress(mCurrentDevice),
1171                                HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
1172                            addQueuedAction(SET_SPEAKER_VOLUME);
1173                        }
1174                    }
1175                    break;
1176                case DIAL_NUMBER:
1177                    // Add the call as an outgoing call.
1178                    BluetoothHeadsetClientCall c = (BluetoothHeadsetClientCall) message.obj;
1179                    mCalls.put(HF_ORIGINATED_CALL_ID, c);
1180
1181                    if (NativeInterface.dialNative(getByteAddress(mCurrentDevice), c.getNumber())) {
1182                        addQueuedAction(DIAL_NUMBER, c.getNumber());
1183                        // Start looping on calling current calls.
1184                        sendMessage(QUERY_CURRENT_CALLS);
1185                    } else {
1186                        Log.e(TAG,
1187                                "ERROR: Cannot dial with a given number:" + (String) message.obj);
1188                        // Set the call to terminated remove.
1189                        c.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
1190                        sendCallChangedIntent(c);
1191                        mCalls.remove(HF_ORIGINATED_CALL_ID);
1192                    }
1193                    break;
1194                case ACCEPT_CALL:
1195                    acceptCall(message.arg1);
1196                    break;
1197                case REJECT_CALL:
1198                    rejectCall();
1199                    break;
1200                case HOLD_CALL:
1201                    holdCall();
1202                    break;
1203                case TERMINATE_CALL:
1204                    terminateCall();
1205                    break;
1206                case ENTER_PRIVATE_MODE:
1207                    enterPrivateMode(message.arg1);
1208                    break;
1209                case EXPLICIT_CALL_TRANSFER:
1210                    explicitCallTransfer();
1211                    break;
1212                case SEND_DTMF:
1213                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice),
1214                            (byte) message.arg1)) {
1215                        addQueuedAction(SEND_DTMF);
1216                    } else {
1217                        Log.e(TAG, "ERROR: Couldn't send DTMF");
1218                    }
1219                    break;
1220                case SUBSCRIBER_INFO:
1221                    if (NativeInterface.retrieveSubscriberInfoNative(
1222                            getByteAddress(mCurrentDevice))) {
1223                        addQueuedAction(SUBSCRIBER_INFO);
1224                    } else {
1225                        Log.e(TAG, "ERROR: Couldn't retrieve subscriber info");
1226                    }
1227                    break;
1228                case QUERY_CURRENT_CALLS:
1229                    // Whenever the timer expires we query calls if there are outstanding requests
1230                    // for query calls.
1231                    long currentElapsed = SystemClock.elapsedRealtime();
1232                    if (mClccTimer < currentElapsed) {
1233                        queryCallsStart();
1234                        mClccTimer = currentElapsed + QUERY_CURRENT_CALLS_WAIT_MILLIS;
1235                        // Request satisfied, ignore all other call query messages.
1236                        removeMessages(QUERY_CURRENT_CALLS);
1237                    } else {
1238                        // Replace all messages with one concrete message.
1239                        removeMessages(QUERY_CURRENT_CALLS);
1240                        sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
1241                    }
1242                    break;
1243                case StackEvent.STACK_EVENT:
1244                    Intent intent = null;
1245                    StackEvent event = (StackEvent) message.obj;
1246                    if (DBG) {
1247                        Log.d(TAG, "Connected: event type: " + event.type);
1248                    }
1249
1250                    switch (event.type) {
1251                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
1252                            if (DBG) {
1253                                Log.d(TAG, "Connected: Connection state changed: " + event.device
1254                                        + ": " + event.valueInt);
1255                            }
1256                            processConnectionEvent(event.valueInt, event.device);
1257                            break;
1258                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
1259                            if (DBG) {
1260                                Log.d(TAG, "Connected: Audio state changed: " + event.device + ": "
1261                                        + event.valueInt);
1262                            }
1263                            processAudioEvent(event.valueInt, event.device);
1264                            break;
1265                        case StackEvent.EVENT_TYPE_NETWORK_STATE:
1266                            if (DBG) {
1267                                Log.d(TAG, "Connected: Network state: " + event.valueInt);
1268                            }
1269                            mIndicatorNetworkState = event.valueInt;
1270
1271                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1272                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
1273                                    event.valueInt);
1274
1275                            if (mIndicatorNetworkState
1276                                    == HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
1277                                mOperatorName = null;
1278                                intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
1279                                        mOperatorName);
1280                            }
1281
1282                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1283                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1284
1285                            if (mIndicatorNetworkState
1286                                    == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
1287                                if (NativeInterface.queryCurrentOperatorNameNative(
1288                                        getByteAddress(mCurrentDevice))) {
1289                                    addQueuedAction(QUERY_OPERATOR_NAME);
1290                                } else {
1291                                    Log.e(TAG, "ERROR: Couldn't querry operator name");
1292                                }
1293                            }
1294                            break;
1295                        case StackEvent.EVENT_TYPE_ROAMING_STATE:
1296                            mIndicatorNetworkType = event.valueInt;
1297
1298                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1299                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
1300                                    event.valueInt);
1301                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1302                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1303                            break;
1304                        case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
1305                            mIndicatorNetworkSignal = event.valueInt;
1306
1307                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1308                            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
1309                                    event.valueInt);
1310                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1311                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1312                            break;
1313                        case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
1314                            mIndicatorBatteryLevel = event.valueInt;
1315
1316                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1317                            intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
1318                                    event.valueInt);
1319                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1320                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1321                            break;
1322                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
1323                            mOperatorName = event.valueString;
1324
1325                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1326                            intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
1327                                    event.valueString);
1328                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1329                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1330                            break;
1331                        case StackEvent.EVENT_TYPE_VR_STATE_CHANGED:
1332                            if (mVoiceRecognitionActive != event.valueInt) {
1333                                mVoiceRecognitionActive = event.valueInt;
1334
1335                                intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1336                                intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
1337                                        mVoiceRecognitionActive);
1338                                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1339                                mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1340                            }
1341                            break;
1342                        case StackEvent.EVENT_TYPE_CALL:
1343                        case StackEvent.EVENT_TYPE_CALLSETUP:
1344                        case StackEvent.EVENT_TYPE_CALLHELD:
1345                        case StackEvent.EVENT_TYPE_RESP_AND_HOLD:
1346                        case StackEvent.EVENT_TYPE_CLIP:
1347                        case StackEvent.EVENT_TYPE_CALL_WAITING:
1348                            sendMessage(QUERY_CURRENT_CALLS);
1349                            break;
1350                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
1351                            queryCallsUpdate(event.valueInt, event.valueInt3, event.valueString,
1352                                    event.valueInt4
1353                                            == HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
1354                                    event.valueInt2
1355                                            == HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
1356                            break;
1357                        case StackEvent.EVENT_TYPE_VOLUME_CHANGED:
1358                            if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
1359                                mCommandedSpeakerVolume = hfToAmVol(event.valueInt2);
1360                                Log.d(TAG, "AM volume set to " + mCommandedSpeakerVolume);
1361                                sAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
1362                                        +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
1363                            } else if (event.valueInt
1364                                    == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
1365                                sAudioManager.setMicrophoneMute(event.valueInt2 == 0);
1366                            }
1367                            break;
1368                        case StackEvent.EVENT_TYPE_CMD_RESULT:
1369                            Pair<Integer, Object> queuedAction = mQueuedActions.poll();
1370
1371                            // should not happen but...
1372                            if (queuedAction == null || queuedAction.first == NO_ACTION) {
1373                                clearPendingAction();
1374                                break;
1375                            }
1376
1377                            if (DBG) {
1378                                Log.d(TAG, "Connected: command result: " + event.valueInt
1379                                        + " queuedAction: " + queuedAction.first);
1380                            }
1381
1382                            switch (queuedAction.first) {
1383                                case QUERY_CURRENT_CALLS:
1384                                    queryCallsDone();
1385                                    break;
1386                                case VOICE_RECOGNITION_START:
1387                                    if (event.valueInt == AT_OK) {
1388                                        mVoiceRecognitionActive =
1389                                                HeadsetClientHalConstants.VR_STATE_STARTED;
1390                                    }
1391                                    break;
1392                                case VOICE_RECOGNITION_STOP:
1393                                    if (event.valueInt == AT_OK) {
1394                                        mVoiceRecognitionActive =
1395                                                HeadsetClientHalConstants.VR_STATE_STOPPED;
1396                                    }
1397                                    break;
1398                                default:
1399                                    Log.w(TAG, "Unhandled AT OK " + event);
1400                                    break;
1401                            }
1402
1403                            break;
1404                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
1405                            mSubscriberInfo = event.valueString;
1406                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
1407                            intent.putExtra(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO,
1408                                    mSubscriberInfo);
1409                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
1410                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1411                            break;
1412                        case StackEvent.EVENT_TYPE_RING_INDICATION:
1413                            // Ringing is not handled at this indication and rather should be
1414                            // implemented (by the client of this service). Use the
1415                            // CALL_STATE_INCOMING (and similar) handle ringing.
1416                            break;
1417                        default:
1418                            Log.e(TAG, "Unknown stack event: " + event.type);
1419                            break;
1420                    }
1421
1422                    break;
1423                default:
1424                    return NOT_HANDLED;
1425            }
1426            return HANDLED;
1427        }
1428
1429        // in Connected state
1430        private void processConnectionEvent(int state, BluetoothDevice device) {
1431            switch (state) {
1432                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
1433                    if (DBG) {
1434                        Log.d(TAG, "Connected disconnects.");
1435                    }
1436                    // AG disconnects
1437                    if (mCurrentDevice.equals(device)) {
1438                        transitionTo(mDisconnected);
1439                    } else {
1440                        Log.e(TAG, "Disconnected from unknown device: " + device);
1441                    }
1442                    break;
1443                default:
1444                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
1445                    break;
1446            }
1447        }
1448
1449        // in Connected state
1450        private void processAudioEvent(int state, BluetoothDevice device) {
1451            // message from old device
1452            if (!mCurrentDevice.equals(device)) {
1453                Log.e(TAG, "Audio changed on disconnected device: " + device);
1454                return;
1455            }
1456
1457            switch (state) {
1458                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED_MSBC:
1459                    mAudioWbs = true;
1460                    // fall through
1461                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED:
1462                    // Audio state is split in two parts, the audio focus is maintained by the
1463                    // entity exercising this service (typically the Telecom stack) and audio
1464                    // routing is handled by the bluetooth stack itself. The only reason to do so is
1465                    // because Bluetooth SCO connection from the HF role is not entirely supported
1466                    // for routing and volume purposes.
1467                    // NOTE: All calls here are routed via the setParameters which changes the
1468                    // routing at the Audio HAL level.
1469
1470                    if (mService.isScoRouted()) {
1471                        StackEvent event =
1472                                new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1473                        event.valueInt = state;
1474                        event.device = device;
1475                        sendMessageDelayed(StackEvent.STACK_EVENT, event, ROUTING_DELAY_MS);
1476                        break;
1477                    }
1478
1479                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED;
1480
1481                    // We need to set the volume after switching into HFP mode as some Audio HALs
1482                    // reset the volume to a known-default on mode switch.
1483                    final int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
1484                    final int hfVol = amToHfVol(amVol);
1485
1486                    if (DBG) {
1487                        Log.d(TAG, "hfp_enable=true mAudioWbs is " + mAudioWbs);
1488                    }
1489                    if (mAudioWbs) {
1490                        if (DBG) {
1491                            Log.d(TAG, "Setting sampling rate as 16000");
1492                        }
1493                        sAudioManager.setParameters("hfp_set_sampling_rate=16000");
1494                    } else {
1495                        if (DBG) {
1496                            Log.d(TAG, "Setting sampling rate as 8000");
1497                        }
1498                        sAudioManager.setParameters("hfp_set_sampling_rate=8000");
1499                    }
1500                    if (DBG) {
1501                        Log.d(TAG, "hf_volume " + hfVol);
1502                    }
1503                    routeHfpAudio(true);
1504                    sAudioManager.setParameters("hfp_volume=" + hfVol);
1505                    transitionTo(mAudioOn);
1506                    break;
1507
1508                case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
1509                    // No state transition is involved, fire broadcast immediately
1510                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTING,
1511                            mAudioState);
1512                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
1513                    break;
1514
1515                case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
1516                    // No state transition is involved, fire broadcast immediately
1517                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1518                            mAudioState);
1519                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1520                    break;
1521
1522                default:
1523                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
1524                    break;
1525            }
1526        }
1527
1528        @Override
1529        public void exit() {
1530            if (DBG) {
1531                Log.d(TAG, "Exit Connected: " + getCurrentMessage().what);
1532            }
1533            mPrevState = this;
1534        }
1535    }
1536
1537    class AudioOn extends State {
1538        @Override
1539        public void enter() {
1540            if (DBG) {
1541                Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
1542            }
1543            broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
1544                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
1545        }
1546
1547        @Override
1548        public synchronized boolean processMessage(Message message) {
1549            if (DBG) {
1550                Log.d(TAG, "AudioOn process message: " + message.what);
1551            }
1552            if (DBG) {
1553                if (mCurrentDevice == null) {
1554                    Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
1555                    return NOT_HANDLED;
1556                }
1557            }
1558
1559            switch (message.what) {
1560                case DISCONNECT:
1561                    BluetoothDevice device = (BluetoothDevice) message.obj;
1562                    if (!mCurrentDevice.equals(device)) {
1563                        break;
1564                    }
1565                    deferMessage(message);
1566                    /*
1567                     * fall through - disconnect audio first then expect
1568                     * deferred DISCONNECT message in Connected state
1569                     */
1570                case DISCONNECT_AUDIO:
1571                    /*
1572                     * just disconnect audio and wait for
1573                     * StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, that triggers State
1574                     * Machines state changing
1575                     */
1576                    if (NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
1577                        routeHfpAudio(false);
1578                    }
1579                    break;
1580
1581                case HOLD_CALL:
1582                    holdCall();
1583                    break;
1584
1585                case StackEvent.STACK_EVENT:
1586                    StackEvent event = (StackEvent) message.obj;
1587                    if (DBG) {
1588                        Log.d(TAG, "AudioOn: event type: " + event.type);
1589                    }
1590                    switch (event.type) {
1591                        case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
1592                            if (DBG) {
1593                                Log.d(TAG, "AudioOn connection state changed" + event.device + ": "
1594                                        + event.valueInt);
1595                            }
1596                            processConnectionEvent(event.valueInt, event.device);
1597                            break;
1598                        case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
1599                            if (DBG) {
1600                                Log.d(TAG, "AudioOn audio state changed" + event.device + ": "
1601                                        + event.valueInt);
1602                            }
1603                            processAudioEvent(event.valueInt, event.device);
1604                            break;
1605                        default:
1606                            return NOT_HANDLED;
1607                    }
1608                    break;
1609                default:
1610                    return NOT_HANDLED;
1611            }
1612            return HANDLED;
1613        }
1614
1615        // in AudioOn state. Can AG disconnect RFCOMM prior to SCO? Handle this
1616        private void processConnectionEvent(int state, BluetoothDevice device) {
1617            switch (state) {
1618                case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
1619                    if (mCurrentDevice.equals(device)) {
1620                        processAudioEvent(HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED,
1621                                device);
1622                        transitionTo(mDisconnected);
1623                    } else {
1624                        Log.e(TAG, "Disconnected from unknown device: " + device);
1625                    }
1626                    break;
1627                default:
1628                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
1629                    break;
1630            }
1631        }
1632
1633        // in AudioOn state
1634        private void processAudioEvent(int state, BluetoothDevice device) {
1635            if (!mCurrentDevice.equals(device)) {
1636                Log.e(TAG, "Audio changed on disconnected device: " + device);
1637                return;
1638            }
1639
1640            switch (state) {
1641                case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
1642                    removeMessages(DISCONNECT_AUDIO);
1643                    mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1644                    // Audio focus may still be held by the entity controlling the actual call
1645                    // (such as Telecom) and hence this will still keep the call around, there
1646                    // is not much we can do here since dropping the call without user consent
1647                    // even if the audio connection snapped may not be a good idea.
1648                    routeHfpAudio(false);
1649                    transitionTo(mConnected);
1650                    break;
1651
1652                default:
1653                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
1654                    break;
1655            }
1656        }
1657
1658        @Override
1659        public void exit() {
1660            if (DBG) {
1661                Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
1662            }
1663            mPrevState = this;
1664            broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
1665                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
1666        }
1667    }
1668
1669    /**
1670     * @hide
1671     */
1672    public synchronized int getConnectionState(BluetoothDevice device) {
1673        if (mCurrentDevice == null) {
1674            return BluetoothProfile.STATE_DISCONNECTED;
1675        }
1676
1677        if (!mCurrentDevice.equals(device)) {
1678            return BluetoothProfile.STATE_DISCONNECTED;
1679        }
1680
1681        IState currentState = getCurrentState();
1682        if (currentState == mConnecting) {
1683            return BluetoothProfile.STATE_CONNECTING;
1684        }
1685
1686        if (currentState == mConnected || currentState == mAudioOn) {
1687            return BluetoothProfile.STATE_CONNECTED;
1688        }
1689
1690        Log.e(TAG, "Bad currentState: " + currentState);
1691        return BluetoothProfile.STATE_DISCONNECTED;
1692    }
1693
1694    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
1695        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
1696        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1697        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1698        if (newState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) {
1699            intent.putExtra(BluetoothHeadsetClient.EXTRA_AUDIO_WBS, mAudioWbs);
1700        }
1701        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1702        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1703        if (DBG) {
1704            Log.d(TAG, "Audio state " + device + ": " + prevState + "->" + newState);
1705        }
1706    }
1707
1708    // This method does not check for error condition (newState == prevState)
1709    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
1710        if (DBG) {
1711            Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
1712        }
1713        /*
1714         * Notifying the connection state change of the profile before sending
1715         * the intent for connection state change, as it was causing a race
1716         * condition, with the UI not being updated with the correct connection
1717         * state.
1718         */
1719        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
1720        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
1721        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
1722        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1723
1724        // add feature extras when connected
1725        if (newState == BluetoothProfile.STATE_CONNECTED) {
1726            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)
1727                    == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
1728                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
1729            }
1730            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
1731                    == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
1732                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
1733            }
1734            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)
1735                    == HeadsetClientHalConstants.PEER_FEAT_ECC) {
1736                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
1737            }
1738
1739            // add individual CHLD support extras
1740            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)
1741                    == HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
1742                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL,
1743                        true);
1744            }
1745            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)
1746                    == HeadsetClientHalConstants.CHLD_FEAT_REL) {
1747                intent.putExtra(
1748                        BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
1749            }
1750            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)
1751                    == HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
1752                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
1753            }
1754            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)
1755                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
1756                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
1757            }
1758            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)
1759                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
1760                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
1761            }
1762        }
1763        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1764    }
1765
1766    boolean isConnected() {
1767        IState currentState = getCurrentState();
1768        return (currentState == mConnected || currentState == mAudioOn);
1769    }
1770
1771    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1772        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
1773        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
1774        int connectionState;
1775        synchronized (this) {
1776            for (BluetoothDevice device : bondedDevices) {
1777                ParcelUuid[] featureUuids = device.getUuids();
1778                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.Handsfree_AG)) {
1779                    continue;
1780                }
1781                connectionState = getConnectionState(device);
1782                for (int state : states) {
1783                    if (connectionState == state) {
1784                        deviceList.add(device);
1785                    }
1786                }
1787            }
1788        }
1789        return deviceList;
1790    }
1791
1792    boolean okToConnect(BluetoothDevice device) {
1793        int priority = mService.getPriority(device);
1794        boolean ret = false;
1795        // check priority and accept or reject the connection. if priority is
1796        // undefined
1797        // it is likely that our SDP has not completed and peer is initiating
1798        // the
1799        // connection. Allow this connection, provided the device is bonded
1800        if ((BluetoothProfile.PRIORITY_OFF < priority) || (
1801                (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
1802                        != BluetoothDevice.BOND_NONE))) {
1803            ret = true;
1804        }
1805        return ret;
1806    }
1807
1808    boolean isAudioOn() {
1809        return (getCurrentState() == mAudioOn);
1810    }
1811
1812    synchronized int getAudioState(BluetoothDevice device) {
1813        if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
1814            return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1815        }
1816        return mAudioState;
1817    }
1818
1819    List<BluetoothDevice> getConnectedDevices() {
1820        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
1821        synchronized (this) {
1822            if (isConnected()) {
1823                devices.add(mCurrentDevice);
1824            }
1825        }
1826        return devices;
1827    }
1828
1829    private byte[] getByteAddress(BluetoothDevice device) {
1830        return Utils.getBytesFromAddress(device.getAddress());
1831    }
1832
1833    public List<BluetoothHeadsetClientCall> getCurrentCalls() {
1834        return new ArrayList<BluetoothHeadsetClientCall>(mCalls.values());
1835    }
1836
1837    public Bundle getCurrentAgEvents() {
1838        Bundle b = new Bundle();
1839        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS, mIndicatorNetworkState);
1840        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, mIndicatorNetworkSignal);
1841        b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING, mIndicatorNetworkType);
1842        b.putInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, mIndicatorBatteryLevel);
1843        b.putString(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME, mOperatorName);
1844        b.putString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO, mSubscriberInfo);
1845        return b;
1846    }
1847}
1848